package com.ruoyi.jarvis.service.goofish;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.erp.request.AuthorizeListQueryRequest;
import com.ruoyi.erp.request.IERPAccount;
import com.ruoyi.erp.request.OrderDetailQueryRequest;
import com.ruoyi.erp.request.OrderListQueryRequest;
import com.ruoyi.erp.request.OrderShipRequest;
import com.ruoyi.jarvis.config.JarvisGoofishProperties;
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.domain.ErpOpenConfig;
import com.ruoyi.jarvis.domain.JDOrder;
import com.ruoyi.jarvis.dto.GoofishShipPreviewVo;
import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper;
import com.ruoyi.jarvis.service.IJDOrderService;
import com.ruoyi.jarvis.service.IErpOpenConfigService;
import com.ruoyi.jarvis.service.erp.ErpAccountResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
 * 闲管家：推送/拉单后的落库、详情、关联京东单、同步运单、发货
 */
@Component
public class GoofishOrderPipeline {

    private static final Logger log = LoggerFactory.getLogger(GoofishOrderPipeline.class);
    private static final String REDIS_WAYBILL_KEY_PREFIX = "logistics:waybill:order:";
    /** F-闲鱼等京东单：运单可按第三方单号=闲鱼单号写入 Redis，见 LogisticsServiceImpl 镜像写入 */
    public static final String REDIS_WAYBILL_GOOFISH_ORDER_PREFIX = "logistics:waybill:goofish:";

    @Resource
    private ErpGoofishOrderMapper erpGoofishOrderMapper;
    @Resource
    private ErpAccountResolver erpAccountResolver;
    @Resource
    private IErpOpenConfigService erpOpenConfigService;
    @Resource
    private IJDOrderService jdOrderService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private JarvisGoofishProperties goofishProperties;
    @Resource
    private GoofishOrderChangeLogger goofishOrderChangeLogger;

    public void runFullPipeline(String appid, JSONObject notifyBody) {
        try {
            JSONObject shape = sourceForNotifyUpsert(notifyBody);
            ErpGoofishOrder row = upsertFromNotify(appid, notifyBody, notifyBody.toJSONString(), "NOTIFY_UPSERT");
            tryLinkJdOrder(row);
            mergeSummaryFromOrderDetailShape(row, shape, "NOTIFY");
            refreshDetail(row);
            syncWaybillFromRedis(row);
            tryAutoShip(row);
        } catch (Exception e) {
            log.error("闲管家订单流水线异常 appid={} body={}", appid, notifyBody, e);
        }
    }

    public void applyListOrNotifyItem(String appKey, JSONObject item, String lastNotifyJson) {
        if (item == null || StringUtils.isEmpty(appKey)) {
            return;
        }
        ErpGoofishOrder row = upsertFromNotify(appKey, item, lastNotifyJson, "LIST_UPSERT");
        tryLinkJdOrder(row);
        mergeSummaryFromOrderDetailShape(row, item, "LIST");
        // 仅待发货等需判物流/发货的状态拉详情；其它状态由推送回调更新，减轻开放平台与本地压力
        List<Integer> awaitingShip = resolveAutoShipOrderStatuses();
        if (row.getOrderStatus() != null && awaitingShip.contains(row.getOrderStatus())) {
            refreshDetail(row);
        }
        syncWaybillFromRedis(row);
        tryAutoShip(row);
    }

    public ErpGoofishOrder upsertFromNotify(String appKey, JSONObject rawBody, String lastNotifyJson) {
        return upsertFromNotify(appKey, rawBody, lastNotifyJson, "NOTIFY_UPSERT");
    }

    public ErpGoofishOrder upsertFromNotify(String appKey, JSONObject rawBody, String lastNotifyJson, String upsertSource) {
        JSONObject body = sourceForNotifyUpsert(rawBody);
        Date now = DateUtils.getNowDate();
        String orderNo = notifyFirstString(body, "order_no", "orderNo");
        if (StringUtils.isEmpty(orderNo)) {
            throw new IllegalArgumentException("缺少 order_no");
        }
        ErpGoofishOrder existing = erpGoofishOrderMapper.selectByAppKeyAndOrderNo(appKey, orderNo);
        ErpGoofishOrder e = new ErpGoofishOrder();
        e.setAppKey(appKey);
        Long sid = notifyFirstLong(body, "seller_id", "sellerId");
        e.setSellerId(sid);
        e.setUserName(notifyFirstString(body, "user_name", "userName", "seller_name"));
        e.setOrderNo(orderNo);
        e.setOrderType(notifyFirstInteger(body, "order_type", "orderType"));
        e.setOrderStatus(notifyFirstInteger(body, "order_status", "orderStatus"));
        e.setRefundStatus(notifyFirstInteger(body, "refund_status", "refundStatus"));
        Long mt = notifyFirstLong(body, "modify_time", "order_modify_time", "update_time", "modifyTime");
        e.setModifyTime(mt);
        Long pid = notifyFirstLong(body, "product_id", "productId");
        e.setProductId(pid);
        Long iid = notifyFirstLong(body, "item_id", "itemId");
        e.setItemId(iid);
        e.setLastNotifyJson(lastNotifyJson);
        e.setUpdateTime(now);
        if (existing == null) {
            e.setCreateTime(now);
            if (e.getShipStatus() == null) {
                e.setShipStatus(0);
            }
            erpGoofishOrderMapper.insert(e);
            ErpGoofishOrder inserted = erpGoofishOrderMapper.selectByAppKeyAndOrderNo(appKey, orderNo);
            logUpsertChange(inserted, null, e, upsertSource);
            return inserted;
        }
        e.setId(existing.getId());
        e.setDetailJson(existing.getDetailJson());
        e.setJdOrderId(existing.getJdOrderId());
        e.setLocalWaybillNo(existing.getLocalWaybillNo());
        e.setShipStatus(existing.getShipStatus());
        e.setShipTime(existing.getShipTime());
        e.setShipError(existing.getShipError());
        e.setShipExpressCode(existing.getShipExpressCode());
        erpGoofishOrderMapper.update(e);
        ErpGoofishOrder updated = erpGoofishOrderMapper.selectByAppKeyAndOrderNo(appKey, orderNo);
        logUpsertChange(updated, existing, e, upsertSource);
        return updated;
    }

    private void logUpsertChange(ErpGoofishOrder loaded, ErpGoofishOrder existingBeforeUpdate, ErpGoofishOrder upsertPayload,
        String upsertSource) {
        if (goofishOrderChangeLogger == null || loaded == null || loaded.getId() == null) {
            return;
        }
        if (existingBeforeUpdate == null) {
            goofishOrderChangeLogger.append(loaded.getId(), loaded.getAppKey(), loaded.getOrderNo(),
                GoofishOrderChangeLogger.TYPE_ORDER, upsertSource,
                "新订单入库，订单状态 " + GoofishStatusLabels.orderStatusHumanForNotify(loaded.getOrderStatus())
                    + "，退款状态 " + GoofishStatusLabels.refundStatusHuman(loaded.getRefundStatus()));
            return;
        }
        boolean refundDiff = !Objects.equals(existingBeforeUpdate.getRefundStatus(), upsertPayload.getRefundStatus());
        boolean orderDiff = !Objects.equals(existingBeforeUpdate.getOrderStatus(), upsertPayload.getOrderStatus());
        if (orderDiff || refundDiff) {
            List<String> parts = new ArrayList<>(2);
            if (orderDiff) {
                parts.add("订单状态 " + GoofishStatusLabels.orderStatusChangeForNotify(
                    existingBeforeUpdate.getOrderStatus(), upsertPayload.getOrderStatus()));
            }
            if (refundDiff) {
                parts.add("退款状态 " + GoofishStatusLabels.refundStatusChange(
                    existingBeforeUpdate.getRefundStatus(), upsertPayload.getRefundStatus()));
            }
            boolean notifyWecom = refundDiff || (orderDiff && GoofishStatusLabels.isWxNotifiableOrderStatusChange(
                existingBeforeUpdate.getOrderStatus(), upsertPayload.getOrderStatus()));
            goofishOrderChangeLogger.append(loaded.getId(), loaded.getAppKey(), loaded.getOrderNo(),
                GoofishOrderChangeLogger.TYPE_ORDER, upsertSource,
                String.join("；", parts), notifyWecom);
        }
    }

    public void tryLinkJdOrder(ErpGoofishOrder row) {
        if (row == null || row.getId() == null || row.getJdOrderId() != null) {
            return;
        }
        String orderNo = row.getOrderNo();
        if (StringUtils.isEmpty(orderNo)) {
            return;
        }
        JDOrder jd = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo.trim());
        if (jd == null || jd.getId() == null) {
            return;
        }
        ErpGoofishOrder patch = new ErpGoofishOrder();
        patch.setId(row.getId());
        patch.setJdOrderId(jd.getId());
        patch.setUpdateTime(DateUtils.getNowDate());
        erpGoofishOrderMapper.update(patch);
        row.setJdOrderId(jd.getId());
    }

    public void refreshDetail(ErpGoofishOrder row) {
        if (row == null || row.getId() == null) {
            return;
        }
        try {
            IERPAccount cred = erpAccountResolver.resolve(row.getAppKey());
            OrderDetailQueryRequest req = new OrderDetailQueryRequest(cred);
            req.setOrderNo(row.getOrderNo());
            String resp = req.getResponseBody();
            ErpGoofishOrder patch = new ErpGoofishOrder();
            patch.setId(row.getId());
            patch.setDetailJson(resp);
            patch.setUpdateTime(DateUtils.getNowDate());
            erpGoofishOrderMapper.update(patch);
            row.setDetailJson(resp);
            JSONObject jo = JSON.parseObject(resp);
            if (jo != null && jo.getIntValue("code") == 0) {
                JSONObject data = jo.getJSONObject("data");
                if (data != null) {
                    mergeSummaryFromOrderDetailShape(row, data, "DETAIL_REFRESH");
                }
            }
        } catch (Exception ex) {
            log.warn("拉取闲管家订单详情失败 id={} orderNo={} err={}", row.getId(), row.getOrderNo(), ex.getMessage());
        }
    }

    /**
     * 合并「订单详情 / 订单列表 / 推送」等同一 schema（order_detail）上的展示与发货摘要字段。
     * 列表与推送含 prov_name、city_name、area_name、town_name、address，详情接口常不含明文地址。
     */
    private void mergeSummaryFromOrderDetailShape(ErpGoofishOrder row, JSONObject data, String logSource) {
        if (row == null || row.getId() == null || data == null) {
            return;
        }
        ErpGoofishOrder beforeSnap = snapshotForEventLog(row);
        ErpGoofishOrder patch = new ErpGoofishOrder();
        patch.setId(row.getId());
        JSONObject goods = data.getJSONObject("goods");
        if (goods != null) {
            String title = goods.getString("title");
            if (StringUtils.isNotEmpty(title)) {
                patch.setGoodsTitle(title.trim());
            }
            String cover = firstGoodsCoverUrl(goods);
            if (StringUtils.isNotEmpty(cover)) {
                patch.setGoodsImageUrl(cover);
            }
            Long gPid = goods.getLong("product_id");
            if (gPid != null) {
                patch.setProductId(gPid);
            }
            Long gIid = goods.getLong("item_id");
            if (gIid != null) {
                patch.setItemId(gIid);
            }
        }
        String wb = data.getString("waybill_no");
        if (StringUtils.isNotEmpty(wb)) {
            patch.setDetailWaybillNo(wb.trim());
        }
        String dec = data.getString("express_code");
        if (StringUtils.isNotEmpty(dec)) {
            patch.setDetailExpressCode(dec.trim());
        }
        String den = data.getString("express_name");
        if (StringUtils.isNotEmpty(den)) {
            patch.setDetailExpressName(den.trim());
        }
        String bn = data.getString("buyer_nick");
        if (StringUtils.isNotEmpty(bn)) {
            patch.setBuyerNick(bn.trim());
        }
        Long payFen = data.getLong("pay_amount");
        if (payFen != null) {
            patch.setPayAmount(payFen);
        }
        String seller = data.getString("seller_name");
        if (StringUtils.isEmpty(row.getUserName()) && StringUtils.isNotEmpty(seller)) {
            patch.setUserName(seller.trim());
        }
        patch.setReceiverName(firstNonEmpty(data, "receiver_name", "ship_name", "consignee_name", "contact_name"));
        patch.setReceiverMobile(firstNonEmpty(data, "receiver_mobile", "ship_mobile", "receiver_phone", "contact_mobile"));
        patch.setReceiverAddress(firstNonEmpty(data, "receiver_address", "ship_address", "detail_address",
            "full_address", "address"));
        String prov = firstNonEmpty(data, "receiver_province", "ship_prov_name", "prov", "prov_name");
        String city = firstNonEmpty(data, "receiver_city", "ship_city_name", "city", "city_name");
        String area = firstNonEmpty(data, "receiver_district", "receiver_area", "ship_area_name", "district", "area_name");
        String town = firstNonEmpty(data, "town_name", "receiver_town", "ship_town_name", "street_name");
        String regionJoined = joinWithSpace(prov, city, area, town);
        if (StringUtils.isNotEmpty(regionJoined)) {
            patch.setReceiverRegion(regionJoined);
        }
        if (StringUtils.isNotEmpty(prov)) {
            patch.setRecvProvName(prov.trim());
        }
        if (StringUtils.isNotEmpty(city)) {
            patch.setRecvCityName(city.trim());
        }
        if (StringUtils.isNotEmpty(area)) {
            patch.setRecvAreaName(area.trim());
        }
        if (StringUtils.isNotEmpty(town)) {
            patch.setRecvTownName(town.trim());
        }
        JSONObject recv = data.getJSONObject("receiver");
        if (recv != null) {
            if (StringUtils.isEmpty(patch.getReceiverName())) {
                patch.setReceiverName(recv.getString("name"));
            }
            if (StringUtils.isEmpty(patch.getReceiverMobile())) {
                String m = recv.getString("mobile");
                if (StringUtils.isEmpty(m)) {
                    m = recv.getString("phone");
                }
                patch.setReceiverMobile(m);
            }
            if (StringUtils.isEmpty(patch.getReceiverAddress())) {
                patch.setReceiverAddress(recv.getString("address"));
            }
        }
        // 开放平台常拆分为省市区 + 明文 detail；明细列为空时用行政区拼接兜底，便于列表与发货解析
        if (StringUtils.isEmpty(patch.getReceiverAddress()) && StringUtils.isNotEmpty(regionJoined)) {
            patch.setReceiverAddress(regionJoined.trim());
        }
        Integer os = firstInt(data, "order_status", "orderStatus");
        if (os != null) {
            patch.setOrderStatus(os);
        }
        Integer rs = firstInt(data, "refund_status", "refundStatus");
        if (rs != null) {
            patch.setRefundStatus(rs);
        }
        Long mt = data.getLong("modify_time");
        if (mt == null) {
            mt = data.getLong("order_modify_time");
        }
        if (mt == null) {
            mt = data.getLong("update_time");
        }
        if (mt != null) {
            patch.setModifyTime(mt);
        }
        patch.setUpdateTime(DateUtils.getNowDate());
        erpGoofishOrderMapper.update(patch);
        applySummaryPatchToRow(row, patch);
        if (goofishOrderChangeLogger != null && logSource != null) {
            goofishOrderChangeLogger.logSummaryMergeDiff(beforeSnap, row, logSource);
        }
    }

    private static ErpGoofishOrder snapshotForEventLog(ErpGoofishOrder r) {
        ErpGoofishOrder s = new ErpGoofishOrder();
        if (r == null) {
            return s;
        }
        s.setOrderStatus(r.getOrderStatus());
        s.setRefundStatus(r.getRefundStatus());
        s.setDetailWaybillNo(r.getDetailWaybillNo());
        s.setDetailExpressCode(r.getDetailExpressCode());
        s.setDetailExpressName(r.getDetailExpressName());
        s.setLocalWaybillNo(r.getLocalWaybillNo());
        return s;
    }

    private void applySummaryPatchToRow(ErpGoofishOrder row, ErpGoofishOrder patch) {
        if (patch.getGoodsTitle() != null) {
            row.setGoodsTitle(patch.getGoodsTitle());
        }
        if (patch.getGoodsImageUrl() != null) {
            row.setGoodsImageUrl(patch.getGoodsImageUrl());
        }
        if (patch.getBuyerNick() != null) {
            row.setBuyerNick(patch.getBuyerNick());
        }
        if (patch.getPayAmount() != null) {
            row.setPayAmount(patch.getPayAmount());
        }
        if (patch.getDetailWaybillNo() != null) {
            row.setDetailWaybillNo(patch.getDetailWaybillNo());
        }
        if (patch.getDetailExpressCode() != null) {
            row.setDetailExpressCode(patch.getDetailExpressCode());
        }
        if (patch.getDetailExpressName() != null) {
            row.setDetailExpressName(patch.getDetailExpressName());
        }
        if (patch.getReceiverName() != null) {
            row.setReceiverName(patch.getReceiverName());
        }
        if (patch.getReceiverMobile() != null) {
            row.setReceiverMobile(patch.getReceiverMobile());
        }
        if (patch.getReceiverAddress() != null) {
            row.setReceiverAddress(patch.getReceiverAddress());
        }
        if (patch.getReceiverRegion() != null) {
            row.setReceiverRegion(patch.getReceiverRegion());
        }
        if (patch.getRecvProvName() != null) {
            row.setRecvProvName(patch.getRecvProvName());
        }
        if (patch.getRecvCityName() != null) {
            row.setRecvCityName(patch.getRecvCityName());
        }
        if (patch.getRecvAreaName() != null) {
            row.setRecvAreaName(patch.getRecvAreaName());
        }
        if (patch.getRecvTownName() != null) {
            row.setRecvTownName(patch.getRecvTownName());
        }
        if (patch.getUserName() != null) {
            row.setUserName(patch.getUserName());
        }
        if (patch.getOrderStatus() != null) {
            row.setOrderStatus(patch.getOrderStatus());
        }
        if (patch.getRefundStatus() != null) {
            row.setRefundStatus(patch.getRefundStatus());
        }
        if (patch.getModifyTime() != null) {
            row.setModifyTime(patch.getModifyTime());
        }
        if (patch.getProductId() != null) {
            row.setProductId(patch.getProductId());
        }
        if (patch.getItemId() != null) {
            row.setItemId(patch.getItemId());
        }
    }

    public void syncWaybillFromRedis(ErpGoofishOrder row) {
        if (row == null || row.getId() == null) {
            return;
        }
        String wb = null;
        if (row.getJdOrderId() != null) {
            String key = REDIS_WAYBILL_KEY_PREFIX + row.getJdOrderId();
            wb = stringRedisTemplate.opsForValue().get(key);
        }
        if (StringUtils.isEmpty(wb) && StringUtils.isNotEmpty(row.getOrderNo())) {
            String gk = REDIS_WAYBILL_GOOFISH_ORDER_PREFIX + row.getOrderNo().trim();
            wb = stringRedisTemplate.opsForValue().get(gk);
        }
        if (wb == null || wb.trim().isEmpty()) {
            return;
        }
        String nw = wb.trim();
        String prevLocal = row.getLocalWaybillNo();
        if (nw.equals(prevLocal != null ? prevLocal.trim() : "")) {
            return;
        }
        ErpGoofishOrder patch = new ErpGoofishOrder();
        patch.setId(row.getId());
        patch.setLocalWaybillNo(nw);
        patch.setUpdateTime(DateUtils.getNowDate());
        erpGoofishOrderMapper.update(patch);
        row.setLocalWaybillNo(nw);
        if (goofishOrderChangeLogger != null) {
            goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(),
                GoofishOrderChangeLogger.TYPE_LOGISTICS, GoofishOrderChangeLogger.SOURCE_REDIS_WAYBILL,
                "本地运单 " + (prevLocal == null || prevLocal.isEmpty() ? "无" : prevLocal) + "→" + nw);
        }
    }

    /**
     * 与前端列表「运单号」展示对齐：除 local/detail 列外，从 detail_json 的 data 根取 waybill_no（列未同步时列表仍可能有单号）。
     */
    private String extractWaybillFromDetailJson(String detailJson) {
        if (StringUtils.isEmpty(detailJson)) {
            return null;
        }
        try {
            JSONObject root = JSON.parseObject(detailJson);
            if (root == null) {
                return null;
            }
            JSONObject data = root.getJSONObject("data");
            if (data == null) {
                data = root;
            }
            String wb = firstNonEmpty(data, "waybill_no", "waybillNo", "mail_no", "mailNo", "express_no", "expressNo");
            return StringUtils.isEmpty(wb) ? null : wb.trim();
        } catch (Exception e) {
            log.debug("从 detail_json 解析运单号失败: {}", e.getMessage());
            return null;
        }
    }

    private String resolveWaybillForShip(ErpGoofishOrder row) {
        String waybill = row.getLocalWaybillNo();
        if (StringUtils.isEmpty(waybill)) {
            waybill = row.getDetailWaybillNo();
        }
        if (StringUtils.isEmpty(waybill)) {
            waybill = extractWaybillFromDetailJson(row.getDetailJson());
        }
        return StringUtils.isEmpty(waybill) ? null : waybill.trim();
    }

    public void tryAutoShip(ErpGoofishOrder row) {
        tryAutoShip(row, false);
    }

    /**
     * @param manualRetry true 时表示管理端「重试发货」：放宽「订单状态须在 auto-ship-order-statuses」校验（与前端列表展示不一致时常因该条件直接 return）
     */
    public void tryAutoShip(ErpGoofishOrder row, boolean manualRetry) {
        if (row == null || row.getId() == null) {
            return;
        }
        List<Integer> awaiting = resolveAutoShipOrderStatuses();
        if (!manualRetry) {
            if (row.getOrderStatus() == null || !awaiting.contains(row.getOrderStatus())) {
                return;
            }
        }
        if (row.getRefundStatus() != null && row.getRefundStatus() != 0) {
            return;
        }
        if (row.getShipStatus() != null && row.getShipStatus() == 1) {
            return;
        }
        String waybill = resolveWaybillForShip(row);
        if (StringUtils.isEmpty(waybill)) {
            return;
        }
        ErpOpenConfig cfg = erpOpenConfigService.selectByAppKey(row.getAppKey());
        String expressCode = cfg != null && StringUtils.isNotEmpty(cfg.getExpressCode()) ? cfg.getExpressCode() : null;
        if (StringUtils.isEmpty(expressCode)) {
            expressCode = goofishProperties.getDefaultShipExpressCode();
        }
        // 业务约定：对闲鱼回传物流名称固定为「日日顺」（编码以配置 / 默认 rrs 为准）
        String expressName = goofishProperties.getDefaultShipExpressName();
        if (StringUtils.isEmpty(expressName)) {
            expressName = "日日顺";
        }
        if (StringUtils.isEmpty(expressCode)) {
            log.info("闲管家自动发货跳过：无快递公司编码（请配置 erp_open_config.express_code 或 jarvis.goofish-order.default-ship-express-code） appKey={} orderNo={}",
                row.getAppKey(), row.getOrderNo());
            return;
        }
        ShipAddressParts addr = resolveShippingAddressParts(row);
        if (StringUtils.isEmpty(addr.shipName) || StringUtils.isEmpty(addr.shipMobile)
            || StringUtils.isEmpty(addr.shipAddress)) {
            patchShipError(row, "缺少收货人/手机/地址：详情无字段时请关联京东单并维护地址，或等平台返回收货字段");
            if (goofishOrderChangeLogger != null) {
                goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(),
                    GoofishOrderChangeLogger.TYPE_SHIP, "AUTO_SHIP",
                    "发货失败（缺地址）：" + (row.getShipError() != null ? row.getShipError() : ""));
            }
            return;
        }
        try {
            IERPAccount cred = erpAccountResolver.resolve(row.getAppKey());
            OrderShipRequest ship = new OrderShipRequest(cred);
            ship.setOrderNo(row.getOrderNo());
            ship.setShipName(addr.shipName);
            ship.setShipMobile(addr.shipMobile);
            ship.setShipAddress(addr.shipAddress);
            if (addr.shipDistrictId != null) {
                ship.setShipDistrictId(addr.shipDistrictId);
            }
            if (StringUtils.isNotEmpty(addr.shipProvName)) {
                ship.setShipProvName(addr.shipProvName);
            }
            if (StringUtils.isNotEmpty(addr.shipCityName)) {
                ship.setShipCityName(addr.shipCityName);
            }
            if (StringUtils.isNotEmpty(addr.shipAreaName)) {
                ship.setShipAreaName(addr.shipAreaName);
            }
            ship.setWaybillNo(waybill.trim());
            ship.setExpressCode(expressCode);
            ship.setExpressName(expressName);
            String resp = ship.getResponseBody();
            JSONObject r = JSON.parseObject(resp);
            if (r != null && r.getIntValue("code") == 0) {
                ErpGoofishOrder ok = new ErpGoofishOrder();
                ok.setId(row.getId());
                ok.setShipStatus(1);
                ok.setShipTime(DateUtils.getNowDate());
                ok.setShipExpressCode(expressCode);
                ok.setShipError(null);
                ok.setUpdateTime(DateUtils.getNowDate());
                erpGoofishOrderMapper.update(ok);
                row.setShipStatus(1);
                if (goofishOrderChangeLogger != null) {
                    goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(),
                        GoofishOrderChangeLogger.TYPE_SHIP, "AUTO_SHIP",
                        "发货成功，运单 " + waybill.trim()
                            + "，订单状态 " + GoofishStatusLabels.orderStatusHuman(row.getOrderStatus())
                            + "，退款状态 " + GoofishStatusLabels.refundStatusHuman(row.getRefundStatus()));
                }
            } else {
                String msg = r != null ? r.getString("msg") : "unknown";
                patchShipError(row, msg != null ? msg : "发货接口返回失败");
                if (goofishOrderChangeLogger != null) {
                    goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(),
                        GoofishOrderChangeLogger.TYPE_SHIP, "AUTO_SHIP",
                        "发货失败：" + (row.getShipError() != null ? row.getShipError() : msg));
                }
            }
        } catch (Exception ex) {
            patchShipError(row, ex.getMessage());
            if (goofishOrderChangeLogger != null) {
                goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(),
                    GoofishOrderChangeLogger.TYPE_SHIP, "AUTO_SHIP",
                    "发货异常：" + (row.getShipError() != null ? row.getShipError() : ex.getMessage()));
            }
            log.warn("闲管家发货异常 orderNo={}", row.getOrderNo(), ex);
        }
    }

    private void patchShipError(ErpGoofishOrder row, String err) {
        ErpGoofishOrder p = new ErpGoofishOrder();
        p.setId(row.getId());
        p.setShipStatus(2);
        String m = err == null ? "" : err;
        if (m.length() > 480) {
            m = m.substring(0, 480) + "…";
        }
        p.setShipError(m);
        p.setUpdateTime(DateUtils.getNowDate());
        erpGoofishOrderMapper.update(p);
        row.setShipStatus(2);
        row.setShipError(m);
    }

    private static class ShipAddressParts {
        String shipName;
        String shipMobile;
        String shipAddress;
        Integer shipDistrictId;
        String shipProvName;
        String shipCityName;
        String shipAreaName;
    }

    private void enrichShipAddressFromJd(ErpGoofishOrder row, ShipAddressParts p) {
        if (row == null || p == null || row.getJdOrderId() == null) {
            return;
        }
        JDOrder jd = jdOrderService.selectJDOrderById(row.getJdOrderId());
        if (jd == null) {
            return;
        }
        if (StringUtils.isEmpty(p.shipAddress) && StringUtils.isNotEmpty(jd.getAddress())) {
            p.shipAddress = jd.getAddress().trim();
        }
        if (StringUtils.isEmpty(p.shipName) && StringUtils.isNotEmpty(jd.getBuyer())) {
            p.shipName = jd.getBuyer().trim();
        }
    }

    private ShipAddressParts receiverFieldsToShipParts(ErpGoofishOrder row) {
        if (row == null) {
            return null;
        }
        String regionLine = row.getReceiverRegion();
        if (StringUtils.isEmpty(regionLine)) {
            regionLine = joinWithSpace(row.getRecvProvName(), row.getRecvCityName(), row.getRecvAreaName(), row.getRecvTownName());
        }
        if (StringUtils.isEmpty(row.getReceiverAddress()) && StringUtils.isEmpty(regionLine)) {
            return null;
        }
        ShipAddressParts p = new ShipAddressParts();
        p.shipName = row.getReceiverName();
        p.shipMobile = row.getReceiverMobile();
        p.shipAddress = row.getReceiverAddress();
        if (StringUtils.isNotEmpty(regionLine)) {
            if (StringUtils.isNotEmpty(p.shipAddress)) {
                p.shipAddress = regionLine + " " + p.shipAddress;
            } else {
                p.shipAddress = regionLine;
            }
        }
        return p;
    }

    private ShipAddressParts parseShipAddress(String detailJson) {
        if (StringUtils.isEmpty(detailJson)) {
            return null;
        }
        JSONObject root = JSON.parseObject(detailJson);
        if (root == null) {
            return null;
        }
        JSONObject data = root.getJSONObject("data");
        if (data == null) {
            data = root;
        }
        ShipAddressParts p = new ShipAddressParts();
        p.shipName = firstNonEmpty(data, "ship_name", "receiver_name", "consignee", "contact_name");
        p.shipMobile = firstNonEmpty(data, "ship_mobile", "receiver_mobile", "receiver_phone", "contact_mobile");
        p.shipAddress = firstNonEmpty(data, "ship_address", "receiver_address", "detail_address", "address");
        p.shipDistrictId = firstInt(data, "ship_district_id", "receiver_district_id");
        p.shipProvName = firstNonEmpty(data, "ship_prov_name", "receiver_province", "prov", "prov_name");
        p.shipCityName = firstNonEmpty(data, "ship_city_name", "receiver_city", "city", "city_name");
        p.shipAreaName = firstNonEmpty(data, "ship_area_name", "receiver_area", "area", "district", "area_name");
        String shipTown = firstNonEmpty(data, "town_name", "ship_town_name", "receiver_town");
        JSONObject recv = data.getJSONObject("receiver");
        if (recv != null) {
            if (StringUtils.isEmpty(p.shipName)) {
                p.shipName = recv.getString("name");
            }
            if (StringUtils.isEmpty(p.shipMobile)) {
                p.shipMobile = recv.getString("mobile");
                if (StringUtils.isEmpty(p.shipMobile)) {
                    p.shipMobile = recv.getString("phone");
                }
            }
            if (StringUtils.isEmpty(p.shipAddress)) {
                p.shipAddress = recv.getString("address");
            }
        }
        mergeRegionIntoShipAddress(p, shipTown);
        return p;
    }

    /**
     * 与列表/落库拆分字段对齐：开放平台若只给省市区镇 + detail，拼成单行供发货调用。
     * 已在 detail 中包含省名开头或与整段行政区一致时不重复前缀。
     */
    private static void mergeRegionIntoShipAddress(ShipAddressParts p, String shipTownName) {
        if (p == null) {
            return;
        }
        String regionLine = joinWithSpace(p.shipProvName, p.shipCityName, p.shipAreaName, shipTownName);
        if (StringUtils.isEmpty(regionLine)) {
            return;
        }
        String detail = p.shipAddress;
        if (StringUtils.isEmpty(detail)) {
            p.shipAddress = regionLine;
            return;
        }
        detail = detail.trim();
        String prov = StringUtils.isNotEmpty(p.shipProvName) ? p.shipProvName.trim() : "";
        if (StringUtils.isNotEmpty(prov) && detail.startsWith(prov)) {
            return;
        }
        if (detail.length() >= regionLine.length() && detail.startsWith(regionLine.trim())) {
            return;
        }
        p.shipAddress = regionLine + " " + detail;
    }

    /**
     * 与 {@link #tryAutoShip(ErpGoofishOrder, boolean)} 对齐：detail_json → 京东兜底 → 列表/库字段补全。
     */
    private ShipAddressParts resolveShippingAddressParts(ErpGoofishOrder row) {
        ShipAddressParts addr = parseShipAddress(row != null ? row.getDetailJson() : null);
        if (addr == null) {
            addr = new ShipAddressParts();
        }
        enrichShipAddressFromJd(row, addr);
        if (StringUtils.isEmpty(addr.shipAddress)) {
            ShipAddressParts fromRow = receiverFieldsToShipParts(row);
            if (fromRow != null) {
                if (StringUtils.isEmpty(addr.shipName)) {
                    addr.shipName = fromRow.shipName;
                }
                if (StringUtils.isEmpty(addr.shipMobile)) {
                    addr.shipMobile = fromRow.shipMobile;
                }
                if (StringUtils.isEmpty(addr.shipAddress)) {
                    addr.shipAddress = fromRow.shipAddress;
                }
            }
        }
        return addr;
    }

    /**
     * 管理端发货预览：闲鱼收件串 vs 实际将提交开放平台地址（可对齐运单回填前自检）。
     */
    public GoofishShipPreviewVo buildShipPreview(ErpGoofishOrder row) {
        GoofishShipPreviewVo vo = new GoofishShipPreviewVo();
        if (row == null) {
            vo.setCompareHint("订单不存在");
            return vo;
        }
        vo.setOrderNo(row.getOrderNo());
        vo.setGoodsTitle(row.getGoodsTitle());
        vo.setJdModelNumber(row.getJdModelNumber());
        vo.setJdAddressFull(row.getJdAddress());

        ShipAddressParts orderSide = receiverFieldsToShipParts(row);
        if (orderSide != null) {
            vo.setGoofishOrderReceiverName(orderSide.shipName);
            vo.setGoofishOrderReceiverMobile(orderSide.shipMobile);
            vo.setGoofishOrderFullAddress(orderSide.shipAddress != null ? orderSide.shipAddress.trim() : "");
        }

        ShipAddressParts ship = resolveShippingAddressParts(row);
        vo.setShipReceiverName(ship.shipName);
        vo.setShipReceiverMobile(ship.shipMobile);
        vo.setShipFullAddress(StringUtils.isNotEmpty(ship.shipAddress) ? ship.shipAddress.trim() : "");

        String nGoofishAddr = normalizeAddrComparable(vo.getGoofishOrderFullAddress());
        String nShipAddr = normalizeAddrComparable(vo.getShipFullAddress());

        boolean goofishHasAddr = StringUtils.isNotEmpty(nGoofishAddr);
        boolean goofishHasContact = StringUtils.isNotEmpty(vo.getGoofishOrderReceiverName())
            || StringUtils.isNotEmpty(vo.getGoofishOrderReceiverMobile());
        vo.setReceiverAddressMatchesGoofish(goofishHasAddr ? Objects.equals(nGoofishAddr, nShipAddr) : null);

        String nJd = normalizeAddrComparable(vo.getJdAddressFull());
        boolean jdHasAddr = StringUtils.isNotEmpty(nJd);
        vo.setReceiverAddressMatchesJd(jdHasAddr ? Objects.equals(nJd, nShipAddr) : null);

        StringBuilder hint = new StringBuilder();
        if (!goofishHasAddr && StringUtils.isNotEmpty(vo.getShipFullAddress())) {
            hint.append("闲鱼侧未合并到收件地址文本，发货参数可能仅靠详情解析或京东补全");
        }
        if (jdHasAddr && Boolean.FALSE.equals(vo.getReceiverAddressMatchesJd())
            && StringUtils.isNotEmpty(vo.getShipFullAddress())) {
            if (hint.length() > 0) {
                hint.append('；');
            }
            hint.append("与京东单整段地址不完全一致（请核对）");
        }
        if (Boolean.FALSE.equals(vo.getReceiverAddressMatchesGoofish())
            && goofishHasAddr && StringUtils.isNotEmpty(vo.getShipFullAddress())) {
            if (hint.length() > 0) {
                hint.append('；');
            }
            hint.append("与闲鱼订单收件串不完全一致（可能已用京东补全或未刷新详情）");
        }
        if (hint.length() == 0) {
            hint.append(goofishHasContact || goofishHasAddr ? "可对照下方两项文本核对收件信息" : "暂无闲鱼收件信息，请先刷新详情或关联京东单");
        }
        vo.setCompareHint(hint.toString());
        return vo;
    }

    private static String normalizeAddrComparable(String addr) {
        if (addr == null) {
            return "";
        }
        return addr.trim().replaceAll("\\s+", " ");
    }


    /** 开放平台 goods.images 首元素，或其它常见单图字段 */
    private static String firstGoodsCoverUrl(JSONObject goods) {
        if (goods == null) {
            return null;
        }
        for (String k : new String[]{"image", "pic", "cover", "main_image", "img", "item_pic"}) {
            String v = goods.getString(k);
            if (StringUtils.isNotEmpty(v)) {
                return v.trim();
            }
        }
        JSONArray images = goods.getJSONArray("images");
        if (images == null || images.isEmpty()) {
            return null;
        }
        Object first = images.get(0);
        if (first == null) {
            return null;
        }
        String u = first.toString().trim();
        return StringUtils.isNotEmpty(u) ? u : null;
    }

    /** 省、市、区、街道等与列表接口 prov_name/city_name/area_name/town_name 对齐拼接 */
    private static String joinWithSpace(String... parts) {
        StringBuilder sb = new StringBuilder();
        for (String p : parts) {
            if (StringUtils.isNotEmpty(p)) {
                if (sb.length() > 0) {
                    sb.append(' ');
                }
                sb.append(p.trim());
            }
        }
        return sb.length() == 0 ? null : sb.toString();
    }

    private static String firstNonEmpty(JSONObject o, String... keys) {
        if (o == null) {
            return null;
        }
        for (String k : keys) {
            String v = o.getString(k);
            if (StringUtils.isNotEmpty(v)) {
                return v.trim();
            }
        }
        return null;
    }

    private static Integer firstInt(JSONObject o, String... keys) {
        if (o == null) {
            return null;
        }
        for (String k : keys) {
            Integer v = o.getInteger(k);
            if (v != null) {
                return v;
            }
        }
        return null;
    }

    private List<Integer> resolveAutoShipOrderStatuses() {
        String raw = goofishProperties.getAutoShipOrderStatuses();
        List<Integer> list = new ArrayList<>();
        if (StringUtils.isNotEmpty(raw)) {
            for (String part : raw.split(",")) {
                String t = part.trim();
                if (t.isEmpty()) {
                    continue;
                }
                try {
                    list.add(Integer.parseInt(t));
                } catch (NumberFormatException ignored) {
                    // ignore invalid token
                }
            }
        }
        if (list.isEmpty()) {
            list.add(12);
        }
        return list;
    }

    /**
     * 推送回调体可能是扁平订单字段，也可能包在 data / data.list[0] / order 中（与列表项 schema 对齐的优先解包）。
     */
    private static JSONObject sourceForNotifyUpsert(JSONObject rawBody) {
        if (rawBody == null) {
            return new JSONObject();
        }
        if (StringUtils.isNotEmpty(rawBody.getString("order_no")) || StringUtils.isNotEmpty(rawBody.getString("orderNo"))) {
            return rawBody;
        }
        JSONObject data = rawBody.getJSONObject("data");
        if (data != null) {
            if (StringUtils.isNotEmpty(data.getString("order_no")) || StringUtils.isNotEmpty(data.getString("orderNo"))) {
                return data;
            }
            JSONObject order = data.getJSONObject("order");
            if (order != null && (StringUtils.isNotEmpty(order.getString("order_no"))
                || StringUtils.isNotEmpty(order.getString("orderNo")))) {
                return order;
            }
            JSONArray list = data.getJSONArray("list");
            if (list != null && !list.isEmpty()) {
                Object first = list.get(0);
                if (first instanceof JSONObject) {
                    JSONObject row = (JSONObject) first;
                    if (StringUtils.isNotEmpty(row.getString("order_no")) || StringUtils.isNotEmpty(row.getString("orderNo"))) {
                        return row;
                    }
                }
            }
        }
        JSONObject order = rawBody.getJSONObject("order");
        if (order != null && (StringUtils.isNotEmpty(order.getString("order_no"))
            || StringUtils.isNotEmpty(order.getString("orderNo")))) {
            return order;
        }
        return rawBody;
    }

    private static String notifyFirstString(JSONObject o, String... keys) {
        return firstNonEmpty(o, keys);
    }

    private static Integer notifyFirstInteger(JSONObject o, String... keys) {
        return firstInt(o, keys);
    }

    private static Long notifyFirstLong(JSONObject o, String... keys) {
        if (o == null) {
            return null;
        }
        for (String k : keys) {
            if (!o.containsKey(k)) {
                continue;
            }
            Long v = o.getLong(k);
            if (v != null) {
                return v;
            }
        }
        return null;
    }

    /**
     * 增量拉单：按 update_time ∈ [now−lookbackHours, now]
     */
    public int pullForAppKey(String appKey, int lookbackHours) {
        if (StringUtils.isEmpty(appKey)) {
            return 0;
        }
        long now = System.currentTimeMillis() / 1000;
        long start = now - (long) lookbackHours * 3600L;
        return pullForAppKeyUpdateTimeRange(appKey, start, now);
    }

    /**
     * 长历史全量：从「当前时间 − pullFullHistoryDays」起，按 pullTimeChunkSeconds 切 update_time 窗口逐段拉取；
     * 每段内 page_size、page_no 与开放平台上限对齐，降低 page_no×page_size 超 1 万的风险。
     */
    public int pullForAppKeyFullHistory(String appKey) {
        if (StringUtils.isEmpty(appKey)) {
            return 0;
        }
        long now = System.currentTimeMillis() / 1000;
        int days = goofishProperties.getPullFullHistoryDays();
        if (days < 1) {
            days = 1;
        }
        long start = now - (long) days * 86400L;
        int maxSpan = goofishProperties.getPullMaxUpdateTimeRangeSeconds();
        if (maxSpan < 86400) {
            maxSpan = 86400;
        }
        int chunk = goofishProperties.getPullTimeChunkSeconds();
        if (chunk < 3600) {
            chunk = 86400;
        }
        chunk = Math.min(chunk, maxSpan);
        int total = 0;
        for (long t = start; t <= now; t += chunk) {
            long end = Math.min(t + chunk - 1, now);
            if (end < t) {
                break;
            }
            total += pullForAppKeyUpdateTimeRange(appKey, t, end);
            try {
                Thread.sleep(150);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return total;
    }

    /**
     * 按 update_time 闭区间拉取；若区间超过开放平台「约 6 个月内」限制，自动拆成多段子区间再请求。
     */
    public int pullForAppKeyUpdateTimeRange(String appKey, long updateTimeStartSec, long updateTimeEndSec) {
        if (StringUtils.isEmpty(appKey) || updateTimeEndSec < updateTimeStartSec) {
            return 0;
        }
        int maxSpan = goofishProperties.getPullMaxUpdateTimeRangeSeconds();
        if (maxSpan < 86400) {
            maxSpan = 86400;
        }
        // 闭区间 [start,end] 共 (end-start+1) 秒，不得超过平台允许的 update_time 跨度（约 6 个月）
        long rangeWidth = updateTimeEndSec - updateTimeStartSec + 1;
        if (rangeWidth <= maxSpan) {
            return pullForAppKeyUpdateTimeRangeOnce(appKey, updateTimeStartSec, updateTimeEndSec);
        }
        int total = 0;
        for (long cur = updateTimeStartSec; cur <= updateTimeEndSec; ) {
            long subEnd = Math.min(cur + maxSpan - 1, updateTimeEndSec);
            total += pullForAppKeyUpdateTimeRangeOnce(appKey, cur, subEnd);
            cur = subEnd + 1;
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
                break;
            }
        }
        return total;
    }

    /**
     * 单次列表请求：update_time 已为平台允许跨度内（闭区间）。
     */
    private int pullForAppKeyUpdateTimeRangeOnce(String appKey, long updateTimeStartSec, long updateTimeEndSec) {
        if (StringUtils.isEmpty(appKey) || updateTimeEndSec < updateTimeStartSec) {
            return 0;
        }
        IERPAccount cred = erpAccountResolver.resolve(appKey);
        List<Long> authorizeIds = fetchAuthorizeIds(cred);
        int pageSize = Math.min(100, Math.max(1, goofishProperties.getPullPageSize()));
        int maxPages = Math.min(100, Math.max(1, goofishProperties.getPullMaxPagesPerShop()));
        if ((long) maxPages * pageSize > 10000L) {
            maxPages = 10000 / pageSize;
            log.warn("闲管家拉单: pull-max-pages 与 pull-page-size 乘积超过 10000，已收敛 maxPages={}", maxPages);
        }
        int saved = 0;
        List<Integer> listStatusFilters = goofishProperties.isPullListOnlyAutoShipStatuses()
            ? resolveAutoShipOrderStatuses()
            : null;
        if (listStatusFilters != null && listStatusFilters.isEmpty()) {
            listStatusFilters = null;
        }
        if (listStatusFilters == null) {
            saved += pullForAppKeyUpdateTimeRangeOnceForStatuses(appKey, cred, authorizeIds, updateTimeStartSec,
                updateTimeEndSec, pageSize, maxPages, null);
        } else {
            for (Integer st : listStatusFilters) {
                saved += pullForAppKeyUpdateTimeRangeOnceForStatuses(appKey, cred, authorizeIds, updateTimeStartSec,
                    updateTimeEndSec, pageSize, maxPages, st);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        return saved;
    }

    /**
     * @param orderStatusFilter null 表示列表请求不带 order_status（拉全状态）
     */
    private int pullForAppKeyUpdateTimeRangeOnceForStatuses(String appKey, IERPAccount cred, List<Long> authorizeIds,
        long updateTimeStartSec, long updateTimeEndSec, int pageSize, int maxPages, Integer orderStatusFilter) {
        int saved = 0;
        for (Long aid : authorizeIds) {
            int page = 1;
            while (page <= maxPages) {
                OrderListQueryRequest q = new OrderListQueryRequest(cred);
                q.setAuthorizeId(aid);
                q.setUpdateTime(updateTimeStartSec, updateTimeEndSec);
                q.setPage(page, pageSize);
                if (orderStatusFilter != null) {
                    q.setOrderStatus(orderStatusFilter);
                }
                String resp;
                try {
                    resp = q.getResponseBody();
                } catch (Exception ex) {
                    log.warn("闲管家拉单失败 aid={} page={} {}", aid, page, ex.getMessage());
                    break;
                }
                JSONObject root = JSON.parseObject(resp);
                if (root == null || root.getIntValue("code") != 0) {
                    log.warn("闲管家拉单接口异常 aid={} page={} resp={}", aid, page, resp);
                    break;
                }
                JSONObject data = root.getJSONObject("data");
                JSONArray list = data == null ? null : data.getJSONArray("list");
                if (list == null || list.isEmpty()) {
                    break;
                }
                for (int i = 0; i < list.size(); i++) {
                    JSONObject item = list.getJSONObject(i);
                    applyListOrNotifyItem(appKey, item, item.toJSONString());
                    saved++;
                }
                if (list.size() < pageSize) {
                    break;
                }
                if (page == maxPages) {
                    log.warn("闲管家拉单已达最大页数 appKey={} aid={} 区间[{},{}] orderStatus={}；若订单更多请缩小 pull-time-chunk-seconds",
                        appKey, aid, updateTimeStartSec, updateTimeEndSec, orderStatusFilter);
                    break;
                }
                page++;
                try {
                    Thread.sleep(200);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        return saved;
    }

    private List<Long> fetchAuthorizeIds(IERPAccount cred) {
        List<Long> ids = new ArrayList<>();
        try {
            AuthorizeListQueryRequest auth = new AuthorizeListQueryRequest(cred);
            auth.setPagination(1, 100);
            String resp = auth.getResponseBody();
            JSONObject root = JSON.parseObject(resp);
            if (root == null || root.getIntValue("code") != 0) {
                log.warn("授权列表查询异常 {}", resp);
                return ids;
            }
            JSONObject data = root.getJSONObject("data");
            JSONArray list = data == null ? null : data.getJSONArray("list");
            if (list == null) {
                return ids;
            }
            for (int i = 0; i < list.size(); i++) {
                JSONObject it = list.getJSONObject(i);
                Long aid = it.getLong("authorize_id");
                if (aid == null) {
                    aid = it.getLong("authorizeId");
                }
                if (aid != null) {
                    ids.add(aid);
                }
            }
        } catch (Exception e) {
            log.warn("拉取授权列表异常 {}", e.getMessage());
        }
        return ids;
    }

    public int syncWaybillAndTryShipBatch(int limit) {
        List<Integer> statuses = resolveAutoShipOrderStatuses();
        List<ErpGoofishOrder> rows = erpGoofishOrderMapper.selectPendingShip(statuses, limit);
        if (rows == null) {
            return 0;
        }
        int n = 0;
        for (ErpGoofishOrder row : rows) {
            ErpGoofishOrder full = erpGoofishOrderMapper.selectById(row.getId());
            if (full == null) {
                continue;
            }
            tryLinkJdOrder(full);
            syncWaybillFromRedis(full);
            if ((full.getRefundStatus() == null || full.getRefundStatus() == 0)
                && statuses.contains(full.getOrderStatus())
                && StringUtils.isEmpty(full.getLocalWaybillNo())
                && StringUtils.isEmpty(full.getDetailWaybillNo())) {
                refreshDetail(full);
                syncWaybillFromRedis(full);
            }
            tryAutoShip(full);
            n++;
        }
        return n;
    }

}
