package com.liquidnet.service.goblin.service.impl.manage;

import com.github.pagehelper.PageInfo;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.candy.constant.CandyRedisConst;
import com.liquidnet.service.candy.dto.CandyUserCouponBasicDto;
import com.liquidnet.service.dragon.constant.DragonConstant;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.entity.GoblinBackOrder;
import com.liquidnet.service.goblin.entity.GoblinBackOrderLog;
import com.liquidnet.service.goblin.param.GoblinStoreOrderRefundParam;
import com.liquidnet.service.goblin.service.impl.helper.GoblinRefundHelper;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreBackOrderService;
import com.liquidnet.service.goblin.util.GoblinMongoUtils;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import com.liquidnet.service.goblin.util.ObjectUtil;
import com.liquidnet.service.goblin.util.QueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.IntStream;

@Service
@Slf4j
public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderService {
    // todo hujiachen 日志没加
    @Autowired
    GoblinRedisUtils redisUtils;
    @Autowired
    GoblinMongoUtils mongoUtils;
    @Autowired
    QueueUtils queueUtils;
    @Autowired
    GoblinRefundHelper refundHelper;

    @Override
    public ResponseDto<GoblinStoreBackOrderListPageVo> orderBackList(Integer page, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, String status) {
        List<GoblinStoreBackOrderListVo> listVos = ObjectUtil.goblinStoreBackOrderListVoArrayList();
        String uid = CurrentUtil.getCurrentUid();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        String storeId = storeInfoVo.getStoreId();
        HashMap<String, Object> map = mongoUtils.storeBackOrderList(storeId, page, orderBackCode, type, cst, cet, orderCode, spuName, status);
        long total = (long) map.get("total");
        List<GoblinBackOrderVo> voList = (List<GoblinBackOrderVo>) map.get("data");
        for (GoblinBackOrderVo item : voList) {
            listVos.add(buildStoreBackOrderListVo(item));
        }
        PageInfo<GoblinStoreBackOrderListVo> pageInfo = new PageInfo(listVos);
        pageInfo.setTotal(total);

        GoblinStoreBackOrderListPageVo result = new GoblinStoreBackOrderListPageVo();
        result.setPageInfo(pageInfo);
        // 审核 Tab 数量：与列表共用筛选条件，但不带 status 参数
        result.setStatusCount(mongoUtils.storeBackOrderStatusCount(storeId, orderBackCode, type, cst, cet, orderCode, spuName));
        return ResponseDto.success(result);
    }

    @Override
    public ResponseDto<GoblinBackOrderDetailsVo> orderDetails(String backOrderId) {
        String uid = CurrentUtil.getCurrentUid();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
        GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(backOrderVo.getOrderId());
        GoblinBackOrderDetailsVo vo = GoblinBackOrderDetailsVo.getNew();
        BeanUtils.copyProperties(backOrderVo, vo);
        vo.setPayTime(orderVo.getPayTime());
        vo.setPriceVoucher(orderVo.getPriceVoucher());
        vo.setPriceExpress(orderVo.getPriceExpress());
        vo.setPriceActual(orderVo.getPriceActual());
        vo.setIsMember(orderVo.getIsMember());
        vo.setUserName(orderVo.getUserName());
        vo.setUserMobile(orderVo.getUserMobile());
        vo.setMailNo(backOrderVo.getMailNo());
        if (orderVo.getMarketId() == null || orderVo.getMarketId().equals("")) {
            vo.setMarketName("");
        } else {
            GoblinSelfMarketingVo marketingVo = redisUtils.getSelfMarket(orderVo.getMarketId().split("ZZ")[1]);
            vo.setMarketName(marketingVo.getName());
        }
        List<GoblinOrderSkuVo> orderSkuVoList = ObjectUtil.getGoblinOrderSkuVoArrayList();
        for (String orderSkuVoId : orderVo.getOrderSkuVoIds()) {
            GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuVoId);
            orderSkuVo.setStatus(vo.getStatus());
            orderSkuVo.desensitize();// 商品订单页面展示-关联人信息脱敏
            orderSkuVoList.add(orderSkuVo);
        }
        vo.setOrderSkuVoList(orderSkuVoList);
        return ResponseDto.success(vo);
    }

    @Override
    public ResponseDto<Boolean> changeExpressRefund(String backOrderId, BigDecimal refundPrice) {
        String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
        BigDecimal sub = backOrderVo.getBackPriceExpress().subtract(refundPrice);
        backOrderVo.setBackPriceExpress(refundPrice);
        backOrderVo.setRealBackPrice(backOrderVo.getRealBackPrice().subtract(sub));
        //添加日志
        GoblinBackOrderLog backOrderLog = initBackLog(backOrderId, uid, now);
        backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue());
        backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
        backOrderLog.setMessage("商户退款-修改快递费：backOrderId=[" + backOrderId + "],refundPrice=[" + refundPrice + "]");

        //redis
        redisUtils.setBackOrderVo(backOrderId, backOrderVo);
        //mongo
        mongoUtils.updateGoblinBackOrderVo(backOrderId, backOrderVo);
        //mysql
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.changeExpress",
                        backOrderVo.getRealBackPrice(), backOrderVo.getBackPriceExpress(), now,
                        backOrderId, now, now
                )
        );
        //添加日志
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.refundLog",
                        backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
                        backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
                )
        );
        return ResponseDto.success();
    }

    @Override
    public ResponseDto<Boolean> agreeRefund(String backOrderId) {
        String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        String nowStr = DateUtil.getNowTime();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
        if (backOrderVo == null) {
            return ResponseDto.failure("不存在");
        }
        GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(backOrderVo.getOrderId());
        if (orderVo == null || !storeInfoVo.getStoreId().equals(orderVo.getStoreId())) {
            return ResponseDto.failure("无法查看");
        }
        if (backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue())) {
            return ResponseDto.failure("已经发起");
        }
        if (!backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue())) {
            return ResponseDto.failure("不可操作");
        }
        backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue());
        backOrderVo.setAuditAt(nowStr);
        //添加日志
        GoblinBackOrderLog backOrderLog = initBackLog(backOrderId, uid, now);
        backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue());
        backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
        backOrderLog.setMessage("商户退款-同意退款：backOrderId=[" + backOrderId + "]");
        redisUtils.setBackOrderVo(backOrderId, backOrderVo);
        mongoUtils.updateGoblinBackOrderVo(backOrderId, backOrderVo);
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.applyRefund",
                        backOrderVo.getStatus(), backOrderVo.getReason(), backOrderVo.getAuditAt(), now,
                        backOrderId, now, now
                )
        );
        String returnString = refundHelper.initRefund(orderVo, backOrderVo.getRealBackPrice(), backOrderVo.getBackCode());
        Boolean success = refundHelper.isRefundSuccess(returnString);
        String message = refundHelper.getRefundMessage(returnString);
        if (!success) {
            backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
            backOrderVo.setErrorReason("失败原因：" + message);
            log.error("REFUND DATA = " + returnString);
            updateStoreRefundOrderAfterPayment(backOrderVo, now);
        }
        //添加日志
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.refundLog",
                        backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
                        backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
                )
        );
        if (success) {
            if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
                refundHelper.alipayCallBack(orderVo, backOrderVo.getBackCode());
            }
            return ResponseDto.success();
        }
        return ResponseDto.failure("退款失败:" + message);
    }

    @Override
    public ResponseDto<Boolean> refusedRefund(String backOrderId) {
        String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        String nowStr = DateUtil.getNowTime();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
        {// 券类商品-拒绝退款后，恢复已发放券状态
            List<GoblinBackOrderSkuVo> backOrderSkuVos = backOrderVo.getBackOrderSkuVos();
            for (GoblinBackOrderSkuVo backOrderSkuVo : backOrderSkuVos) {
                if (null != backOrderSkuVo.getSkuType() && 2 == backOrderSkuVo.getSkuType()) {
                    // 券类商品默认一个商品对应一个券，下单只可购买一张
                    String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(backOrderVo.getUserId());
                    List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
                    int idx = IntStream.range(0, vos.size())
                            .filter(i -> vos.get(i).getUcouponId().equals(backOrderSkuVo.getOrderSkuId())).findFirst().orElse(-1);
                    if (-1 != idx) {
                        CandyUserCouponBasicDto basicDto = vos.get(idx);
                        basicDto.setState(1);// 恢复状态
                        vos.set(idx, basicDto);
                        redisUtils.redisUtil.set(ucKey, vos);

                        queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                                SqlMapping.get("candy_user_coupon.update_apply_refund",
                                        basicDto.getState(), uid, now, basicDto.getUcouponId()
                                )
                        );
                    } else {
                        log.warn("券类商品订单拒绝退款，未找到对应券[orderSkuId={},operator={}]", backOrderSkuVo.getOrderSkuId(), uid);
                    }
                }
            }
        }
        // 拒绝退款后恢复子单状态（61 -> 2/3/4），否则再次发起退款会因 canRefund 校验失败
        GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(backOrderVo.getOrderId());
        LinkedList<Object[]> orderSkuStatusSql = restoreOrderSkuStatusOnRefundRefused(backOrderVo, orderVo, now);
        backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_3.getValue());
        backOrderVo.setRefuseAt(nowStr);
        backOrderVo.setAuditAt(nowStr);
        backOrderVo.setRefuseSize(backOrderVo.getRefuseSize() == null ? 1 : backOrderVo.getRefuseSize() + 1);
        //添加日志
        GoblinBackOrderLog backOrderLog = initBackLog(backOrderId, uid, now);
        backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue());
        backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
        backOrderLog.setMessage("商户退款-拒绝退款：backOrderId=[" + backOrderId + "]");
        //redis
        redisUtils.setBackOrderVo(backOrderId, backOrderVo);
        //mongo
        mongoUtils.updateGoblinBackOrderVo(backOrderId, backOrderVo);
        //mysql
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> backOrderStatusSql = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> refundLogSql = CollectionUtil.linkedListObjectArr();
        sqls.add(SqlMapping.get("goblin_order.store.backOrderStatus"));
        backOrderStatusSql.add(new Object[]{
                backOrderVo.getStatus(), backOrderVo.getAuditAt(), backOrderVo.getRefuseSize(), now,
                backOrderId, now, now
        });
        if (!orderSkuStatusSql.isEmpty()) {
            sqls.add(SqlMapping.get("goblin_order.store.orderSkuStatus"));
        }
        sqls.add(SqlMapping.get("goblin_order.store.refundLog"));
        refundLogSql.add(new Object[]{
                backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
                backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
        });
        if (orderSkuStatusSql.isEmpty()) {
            queueUtils.sendMsgByRedis(
                    MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                    SqlMapping.gets(sqls, backOrderStatusSql, refundLogSql)
            );
        } else {
            queueUtils.sendMsgByRedis(
                    MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                    SqlMapping.gets(sqls, backOrderStatusSql, orderSkuStatusSql, refundLogSql)
            );
        }
        return ResponseDto.success();
    }

    /**
     * 管理端新退款入口。
     * 发货前（子单全为待发货）：整单 + 运费，自动退款；
     * 发货后（存在待收货/已完成子单）：可选商品，不含运费，生成待审核退款单。
     */
    @Override
    public ResponseDto<Boolean> refundOrder(GoblinStoreOrderRefundParam param) {
        String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        String nowStr = DateUtil.getNowTime();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(param.getOrderId());
        if (orderVo == null || !storeInfoVo.getStoreId().equals(orderVo.getStoreId())) {
            return ResponseDto.failure("无法查看");
        }
        if (!orderVo.getMixId().isEmpty()) {
            return ResponseDto.failure("组合购商品暂不支持退款");
        }

        // 分布式锁防并发重复退款
        String lockKey = GoblinRedisConst.REFUND_ORDER_LOCK + param.getOrderId();
        if (!redisUtils.redisUtil.lock(lockKey, 1, 30)) {
            return ResponseDto.failure("退款处理中，请勿重复操作");
        }
        try {
            if (hasProcessingBackOrder(orderVo.getOrderId())) {
                return ResponseDto.failure("已有退款处理中");
            }

            // 管理端发起退款：根据子单状态决定「发货前整单自动退」还是「发货后部分退待审核」
            List<GoblinOrderSkuVo> orderSkuVos = getOrderSkuVos(orderVo);
            // 发货前：所有子单均为 status=2（待发货）→ 强制整单退、含运费、自动调支付
            boolean beforeShip = orderSkuVos.stream().allMatch(GoblinRefundHelper::isUnshippedSku);
            // 是否允许发起：全部待发货，或至少有一个子单已发货/已完成（status=3/4）
            // 注意：若存在已退完子单（status=6）与待发货子单混合（如旧接口部分退后），此处会判为不可退，需单独兼容
            boolean canRefund = beforeShip || orderSkuVos.stream().anyMatch(GoblinRefundHelper::isShippedSku);
            if (!canRefund) {
                return ResponseDto.failure("不可退款");
            }

            // 发货前忽略 orderSkuIds，整单退；发货后按入参选择子单（不传则默认整单剩余可退商品）
            List<GoblinOrderSkuVo> refundSkuVos = beforeShip ? orderSkuVos : getSelectedOrderSkuVos(orderVo, orderSkuVos, param.getOrderSkuIds());
            if (refundSkuVos.isEmpty()) {
                return ResponseDto.failure("请选择退款商品");
            }
            return createStoreRefundOrder(orderVo, refundSkuVos, beforeShip, param, uid, now, nowStr);
        } finally {
            redisUtils.redisUtil.uLock(lockKey);
        }
    }

    @Override
    public ResponseDto<Boolean> changeSkuRefund(String backOrderId, BigDecimal refundPrice, String orderSkuId) {
        String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
        if (storeInfoVo == null) {
            return ResponseDto.failure("无法查看");
        }
        BigDecimal sub = BigDecimal.ZERO;
        GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
        for (GoblinBackOrderSkuVo backOrderSkuVo : backOrderVo.getBackOrderSkuVos()) {
            if (backOrderSkuVo.getOrderSkuId().equals(orderSkuId)) {
                sub = backOrderSkuVo.getRefundPrice().subtract(refundPrice);
                backOrderSkuVo.setRefundPrice(refundPrice);
                break;
            }
        }
        backOrderVo.setRealBackPrice(backOrderVo.getRealBackPrice().subtract(sub));
        //添加日志
        GoblinBackOrderLog backOrderLog = initBackLog(backOrderId, uid, now);
        backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue());
        backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
        backOrderLog.setMessage("商户退款-修改sku价格：backOrderId=[" + backOrderId + "],refundPrice=[" + refundPrice + "],orderSkuId=[" + orderSkuId + "]");
        //redis
        redisUtils.setBackOrderVo(backOrderId, backOrderVo);
        //mongo
        mongoUtils.updateGoblinBackOrderVo(backOrderId, backOrderVo);
        //mysql
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.changeSku",
                        backOrderVo.getRealBackPrice(), now,
                        backOrderId, now, now
                )
        );
        //添加日志
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.refundLog",
                        backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
                        backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
                )
        );
        return ResponseDto.success();
    }

    private boolean hasProcessingBackOrder(String orderId) {
        List<String> backOrderIds = redisUtils.getBackOrderByOrderId(orderId);
        for (String backOrderId : backOrderIds) {
            GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
            if (backOrderVo != null && !GoblinRefundHelper.isBackOrderFinished(backOrderVo.getStatus())) {
                return true;
            }
        }
        return false;
    }

    private List<GoblinOrderSkuVo> getOrderSkuVos(GoblinStoreOrderVo orderVo) {
        List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
        for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
            GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
            if (orderSkuVo != null) {
                orderSkuVos.add(orderSkuVo);
            }
        }
        return orderSkuVos;
    }

    private List<GoblinOrderSkuVo> getSelectedOrderSkuVos(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> orderSkuVos, List<String> orderSkuIds) {
        if (orderSkuIds == null || orderSkuIds.isEmpty()) {
            return orderSkuVos;
        }
        Set<String> orderSkuIdSet = new HashSet<>(orderVo.getOrderSkuVoIds());
        Set<String> selectedIdSet = new HashSet<>(orderSkuIds);
        List<GoblinOrderSkuVo> selectedSkuVos = new ArrayList<>();
        for (String orderSkuId : selectedIdSet) {
            if (!orderSkuIdSet.contains(orderSkuId)) {
                return new ArrayList<>();
            }
        }
        for (GoblinOrderSkuVo orderSkuVo : orderSkuVos) {
            if (selectedIdSet.contains(orderSkuVo.getOrderSkuId())) {
                selectedSkuVos.add(orderSkuVo);
            }
        }
        return selectedSkuVos;
    }

    private ResponseDto<Boolean> createStoreRefundOrder(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> refundSkuVos,
                                                        boolean beforeShip, GoblinStoreOrderRefundParam param,
                                                        String uid, LocalDateTime now, String nowStr) {
        BigDecimal skuRefundPrice = BigDecimal.ZERO;
        List<String> orderSkuIds = new ArrayList<>();
        List<GoblinBackOrderSkuVo> backOrderSkuVos = ObjectUtil.goblinBackOrderSkuVoArrayList();
        boolean selectAll = param.getOrderSkuIds() == null || param.getOrderSkuIds().isEmpty();
        for (GoblinOrderSkuVo orderSkuVo : refundSkuVos) {
            BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuVo.getOrderSkuId());
            BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
            if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
                if (!beforeShip && selectAll) {
                    continue;
                }
                return ResponseDto.failure("退款价格超过商品可退价格");
            }
            skuRefundPrice = skuRefundPrice.add(refundPrice);
            orderSkuIds.add(orderSkuVo.getOrderSkuId());
            backOrderSkuVos.add(GoblinRefundHelper.buildBackOrderSkuVo(orderSkuVo, refundPrice, nowStr));
        }
        if (backOrderSkuVos.isEmpty()) {
            return ResponseDto.failure("退款价格超过商品可退价格");
        }

        BigDecimal backPriceExpress = beforeShip && orderVo.getPriceExpress() != null ? orderVo.getPriceExpress() : BigDecimal.ZERO;
        BigDecimal realBackPrice = beforeShip ? GoblinRefundHelper.beforeShipRefundPrice(skuRefundPrice, backPriceExpress) : skuRefundPrice;
        int backOrderStatus = beforeShip ? GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue() : GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue();
        String refundCode = IDGenerator.storeRefundCode(orderVo.getMasterOrderCode());

        GoblinBackOrder backOrder = GoblinBackOrder.getNew();
        backOrder.setBackOrderId(IDGenerator.nextTimeId2());
        backOrder.setBackCode(refundCode);
        backOrder.setOrderId(orderVo.getOrderId());
        backOrder.setOrderCode(orderVo.getOrderCode());
        backOrder.setStoreId(orderVo.getStoreId());
        backOrder.setUserId(orderVo.getUserId());
        backOrder.setSkuIdNums(String.join(",", orderSkuIds));
        backOrder.setType(GoblinStatusConst.Type.BACK_TYPE_1.getValue());
        backOrder.setReason(StringUtil.isBlank(param.getReason()) ? GoblinStatusConst.Type.BACK_REASON_TYPE_8.getDesc() : param.getReason());
        backOrder.setDescribes(StringUtil.isBlank(param.getDescribes()) ? "店铺退款" : param.getDescribes());
        backOrder.setRealBackPrice(realBackPrice);
        backOrder.setBackPriceExpress(backPriceExpress);
        backOrder.setStatus(backOrderStatus);
        backOrder.setCreatedAt(now);
        if (beforeShip) {
            backOrder.setAuditAt(now);
        }

        GoblinBackOrderVo backOrderVo = GoblinBackOrderVo.getNew();
        BeanUtils.copyProperties(backOrder, backOrderVo);
        backOrderVo.setCreatedAt(nowStr);
        backOrderVo.setAuditAt(beforeShip ? nowStr : null);
        backOrderVo.setBackPriceExpress(backPriceExpress);
        backOrderVo.setBackOrderSkuVos(backOrderSkuVos);
        backOrderVo.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());

        GoblinBackOrderLog backOrderLog = initBackLog(backOrder.getBackOrderId(), uid, now);
        backOrderLog.setStatus(beforeShip ? GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue() : GoblinStatusConst.Status.ORDER_LOG_STATUS_20.getValue());
        backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
        backOrderLog.setMessage("商户发起退款：orderSkuIds=[" + backOrder.getSkuIdNums() + "],refundPrice=[" + realBackPrice + "],[refundCode=" + refundCode + "]");

        saveStoreRefundOrder(orderVo, refundSkuVos, backOrder, backOrderVo, backOrderLog, now);
        if (beforeShip) {
            String returnString = refundHelper.initRefund(orderVo, realBackPrice, refundCode);
            if (!refundHelper.isRefundSuccess(returnString)) {
                backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
                backOrderVo.setErrorReason("失败原因：" + refundHelper.getRefundMessage(returnString));
                backOrder.setStatus(backOrderVo.getStatus());
                backOrder.setErrorReason(backOrderVo.getErrorReason());
                log.error("REFUND DATA = " + returnString);
                updateStoreRefundOrderAfterPayment(backOrderVo, now);
            } else if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
                refundHelper.alipayCallBack(orderVo, refundCode);
            }
        }
        return backOrderVo.getStatus() == GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue()
                ? ResponseDto.failure("退款失败:" + backOrderVo.getErrorReason())
                : ResponseDto.success();
    }

    private void saveStoreRefundOrder(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> refundSkuVos,
                                      GoblinBackOrder backOrder, GoblinBackOrderVo backOrderVo,
                                      GoblinBackOrderLog backOrderLog, LocalDateTime now) {
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        sqls.add(SqlMapping.get("goblin_order.store.backOrderWithExpress"));
        sqls.add(SqlMapping.get("goblin_order.store.orderSkuStatus"));
        sqls.add(SqlMapping.get("goblin_order.store.refundLog"));

        LinkedList<Object[]> backOrderSql = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> orderSkuStatusSql = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> refundLogSql = CollectionUtil.linkedListObjectArr();

        backOrderSql.add(new Object[]{
                backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(),
                backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(),
                backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
                backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(),
                backOrder.getStatus(), backOrder.getPics(), backOrder.getCreatedAt(),
                backOrder.getAuditAt(), backOrder.getErrorReason()
        });

        for (GoblinOrderSkuVo orderSkuVo : refundSkuVos) {
            orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
            redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
            mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
            orderSkuStatusSql.add(new Object[]{
                    orderSkuVo.getStatus(), now,
                    orderSkuVo.getOrderSkuId(), now, now
            });
        }
        refundLogSql.add(new Object[]{
                backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
                backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
        });

        redisUtils.setBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
        redisUtils.addBackOrderByOrderId(orderVo.getOrderId(), backOrderVo.getBackOrderId());
        mongoUtils.insertGoblinBackOrderVo(backOrderVo);
        queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.gets(sqls, backOrderSql, orderSkuStatusSql, refundLogSql));
    }

    private void updateStoreRefundOrderAfterPayment(GoblinBackOrderVo backOrderVo, LocalDateTime now) {
        redisUtils.setBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
        mongoUtils.updateGoblinBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
        queueUtils.sendMsgByRedis(
                MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
                SqlMapping.get("goblin_order.store.applyRefund",
                        backOrderVo.getStatus(), backOrderVo.getReason(), backOrderVo.getAuditAt(), now,
                        backOrderVo.getBackOrderId(), now, now
                )
        );
    }

    private GoblinStoreBackOrderListVo buildStoreBackOrderListVo(GoblinBackOrderVo item) {
        GoblinStoreBackOrderListVo vo = GoblinStoreBackOrderListVo.getNew();
        vo.setBackOrderId(item.getBackOrderId());
        vo.setBackCode(item.getBackCode());
        vo.setOrderCode(item.getOrderCode());
        vo.setType(item.getType());
        vo.setStatus(item.getStatus());
        vo.setRealBackPrice(item.getRealBackPrice());
        vo.setCreatedAt(item.getCreatedAt());
        vo.setAuditAt(item.getAuditAt());
        vo.setRefundAt(item.getRefundAt());
        vo.setBackOrderSkuVos(item.getBackOrderSkuVos());
        if (StringUtil.isNotBlank(item.getRefundAt())) {
            vo.setProcessedAt(item.getRefundAt());
        } else if (StringUtil.isNotBlank(item.getAuditAt())) {
            vo.setProcessedAt(item.getAuditAt());
        } else if (StringUtil.isNotBlank(item.getRefuseAt())) {
            vo.setProcessedAt(item.getRefuseAt());
        }
        vo.setOperationType(GoblinRefundHelper.resolveBackOrderOperationType(item));
        if (StringUtil.isNotBlank(item.getOrderId())) {
            GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(item.getOrderId());
            if (orderVo != null) {
                vo.setPriceActual(orderVo.getPriceActual());
                int orderSkuCount = orderVo.getOrderSkuVoIds() == null ? 0 : orderVo.getOrderSkuVoIds().size();
                int refundSkuCount = item.getBackOrderSkuVos() == null ? 0 : item.getBackOrderSkuVos().size();
                boolean fullRefund = orderSkuCount > 0 && refundSkuCount >= orderSkuCount;
                vo.setRefundScope(fullRefund ? 1 : 2);
            }
        }
        return vo;
    }

    private LinkedList<Object[]> restoreOrderSkuStatusOnRefundRefused(GoblinBackOrderVo backOrderVo,
                                                                      GoblinStoreOrderVo orderVo,
                                                                      LocalDateTime now) {
        LinkedList<Object[]> orderSkuStatusSql = CollectionUtil.linkedListObjectArr();
        if (backOrderVo == null || orderVo == null || backOrderVo.getBackOrderSkuVos() == null) {
            return orderSkuStatusSql;
        }
        for (GoblinBackOrderSkuVo backOrderSkuVo : backOrderVo.getBackOrderSkuVos()) {
            GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(backOrderSkuVo.getOrderSkuId());
            if (orderSkuVo == null) {
                continue;
            }
            if (!Integer.valueOf(GoblinStatusConst.Status.ORDER_STATUS_61.getValue()).equals(orderSkuVo.getStatus())) {
                continue;
            }
            orderSkuVo.setStatus(resolveSkuRollbackStatus(orderVo, orderSkuVo.getOrderSkuId()));
            redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
            mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
            orderSkuStatusSql.add(new Object[]{
                    orderSkuVo.getStatus(), now,
                    orderSkuVo.getOrderSkuId(), now, now
            });
        }
        return orderSkuStatusSql;
    }

    private int resolveSkuRollbackStatus(GoblinStoreOrderVo orderVo, String orderSkuId) {
        int orderStatus = orderVo.getStatus();
        if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
            return isSkuShipped(orderVo.getOrderId(), orderSkuId)
                    ? GoblinStatusConst.Status.ORDER_STATUS_3.getValue()
                    : GoblinStatusConst.Status.ORDER_STATUS_2.getValue();
        }
        if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_3.getValue()) {
            return GoblinStatusConst.Status.ORDER_STATUS_3.getValue();
        }
        if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {
            return GoblinStatusConst.Status.ORDER_STATUS_4.getValue();
        }
        return orderStatus;
    }

    private boolean isSkuShipped(String orderId, String orderSkuId) {
        List<GoblinMailVo> mails = redisUtils.getGoblinMail(orderId);
        if (mails == null || mails.isEmpty()) {
            return false;
        }
        for (GoblinMailVo mail : mails) {
            if (mail.getOrderSkuVoIds() != null && mail.getOrderSkuVoIds().contains(orderSkuId)) {
                return true;
            }
        }
        return false;
    }

    private GoblinBackOrderLog initBackLog(String orderId, String uid, LocalDateTime now) {
        GoblinBackOrderLog log = GoblinBackOrderLog.getNew();
        log.setBackOrderId(orderId);
        log.setOperationName(uid);
        log.setBackOrderLogId(IDGenerator.nextTimeId2());
        log.setCreatedAt(now);
        return log;
    }
}
