package cn.qihangerp.service.external.pdd;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 解析拼多多 POP 返回 JSON 的通用工具。
 *
 * @author guochengyu
 */
public final class PddOpenApiSupport {

    private PddOpenApiSupport() {
    }

    /**
     * {@code pdd.goods.cat.rule.get} 的<strong>表单顶层</strong>业务参数（与官方 POP curl / Java SDK 一致，不走 {@code param_json}）。
     * <p>新发品无拼多多 {@code goods_id} 时传 {@code "0"}。</p>
     */
    public static Map<String, String> catRuleGetTopLevelParams(long catId) {
        if (catId <= 0) {
            throw new IllegalArgumentException("cat_id 必须为正数: " + catId);
        }
        Map<String, String> m = new LinkedHashMap<>();
        m.put("cat_id", String.valueOf(catId));
        m.put("goods_id", "0");
        return m;
    }

    /**
     * {@code pdd.goods.detail.get} 表单顶层参数（与 POP 文档一致：{@code goods_id}）。
     */
    public static Map<String, String> goodsDetailGetTopLevelParams(long goodsId) {
        if (goodsId <= 0) {
            throw new IllegalArgumentException("goods_id 必须为正数: " + goodsId);
        }
        Map<String, String> m = new LinkedHashMap<>();
        m.put("goods_id", String.valueOf(goodsId));
        return m;
    }

    /**
     * {@code pdd.goods.quantity.update} 表单顶层参数（与 POP 官方 curl / Java SDK 一致：{@code goods_id}、{@code sku_id}、{@code quantity}、
     * {@code update_type}、{@code force_update}、可选 {@code outer_id}）。
     */
    public static Map<String, String> goodsQuantityUpdateTopLevelParams(long goodsId, Long skuId, String outerId,
                                                                        long quantity, int updateType, boolean forceUpdate) {
        if (goodsId <= 0) {
            throw new IllegalArgumentException("goods_id 必须为正数: " + goodsId);
        }
        if (quantity < 0) {
            throw new IllegalArgumentException("quantity 不能为负: " + quantity);
        }
        Map<String, String> m = new LinkedHashMap<>();
        m.put("goods_id", String.valueOf(goodsId));
        if (skuId != null && skuId > 0) {
            m.put("sku_id", String.valueOf(skuId));
        }
        if (StringUtils.hasText(outerId)) {
            m.put("outer_id", outerId.trim());
        }
        m.put("quantity", String.valueOf(quantity));
        m.put("update_type", String.valueOf(updateType));
        m.put("force_update", forceUpdate ? "true" : "false");
        return m;
    }

    /**
     * 解析 {@code pdd.goods.quantity.update} 响应中的 {@code goods_quantity_update_response.is_success}。
     */
    public static boolean parseGoodsQuantityUpdateIsSuccess(String raw) {
        if (!StringUtils.hasText(raw)) {
            return false;
        }
        try {
            JSONObject root = JSON.parseObject(raw);
            if (root == null) {
                return false;
            }
            JSONObject resp = root.getJSONObject("goods_quantity_update_response");
            if (resp == null) {
                return false;
            }
            Object v = resp.get("is_success");
            if (v instanceof Boolean b) {
                return b;
            }
            if (v != null) {
                return "true".equalsIgnoreCase(String.valueOf(v).trim());
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 从 {@code pdd.goods.add} 原始响应 JSON 中解析 {@code goods_add_response.goods_id}（拼多多侧商品 ID）。
     */
    public static Long parseGoodsIdFromGoodsAddResponse(String raw) {
        if (!StringUtils.hasText(raw)) {
            return null;
        }
        try {
            JSONObject root = JSON.parseObject(raw);
            if (root == null) {
                return null;
            }
            JSONObject add = root.getJSONObject("goods_add_response");
            if (add == null) {
                return null;
            }
            Long id = add.getLong("goods_id");
            return id != null && id > 0 ? id : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 从 {@code pdd.goods.add} 成功响应中解析 {@code goods_add_response.sku_list[]}：
     * 按 {@code out_sku_sn}/{@code outer_id}/{@code out_sku_id} 与外部 {@code outSkuId} 对齐，映射到拼多多 {@code sku_id}。
     */
    public static Map<String, Long> parseOuterKeyToPddSkuIdFromGoodsAddResponse(String raw) {
        Map<String, Long> out = new LinkedHashMap<>();
        if (!StringUtils.hasText(raw)) {
            return out;
        }
        try {
            JSONObject root = JSON.parseObject(raw);
            if (root == null) {
                return out;
            }
            JSONObject add = root.getJSONObject("goods_add_response");
            if (add == null) {
                return out;
            }
            JSONArray skuList = add.getJSONArray("sku_list");
            if (skuList == null || skuList.isEmpty()) {
                return out;
            }
            for (int i = 0; i < skuList.size(); i++) {
                JSONObject sku = skuList.getJSONObject(i);
                if (sku == null) {
                    continue;
                }
                Long skuId = sku.getLong("sku_id");
                if (skuId == null || skuId <= 0) {
                    continue;
                }
                String outerKey = firstNonBlank(
                        sku.getString("out_sku_sn"),
                        sku.getString("outer_id"),
                        sku.getString("out_sku_id"));
                if (!StringUtils.hasText(outerKey)) {
                    continue;
                }
                out.putIfAbsent(outerKey.trim(), skuId);
            }
        } catch (Exception ignored) {
            // ignore
        }
        return out;
    }

    /**
     * 从 {@code pdd.goods.detail.get} 成功响应中解析 {@code goods_detail_get_response.sku_list[]}，
     * 与 {@link #parseOuterKeyToPddSkuIdFromGoodsAddResponse} 对齐：按 {@code out_sku_sn}/{@code outer_id}/{@code out_sku_id} 映射拼多多 {@code sku_id}。
     * <p>用于 {@code INFORMATION_UPDATE} 等未返回 {@code goods_add_response.sku_list} 的发品路径之后，在 ERP 本地补全 {@code o_goods_sku.canonicalExt.pddSkuId}。</p>
     */
    public static Map<String, Long> parseOuterKeyToPddSkuIdFromGoodsDetailGetResponse(String raw) {
        Map<String, Long> out = new LinkedHashMap<>();
        if (!StringUtils.hasText(raw)) {
            return out;
        }
        try {
            JSONObject root = JSON.parseObject(raw);
            if (root == null) {
                return out;
            }
            JSONObject detail = root.getJSONObject("goods_detail_get_response");
            if (detail == null) {
                return out;
            }
            JSONArray skuList = detail.getJSONArray("sku_list");
            if (skuList == null || skuList.isEmpty()) {
                return out;
            }
            for (int i = 0; i < skuList.size(); i++) {
                JSONObject sku = skuList.getJSONObject(i);
                if (sku == null) {
                    continue;
                }
                Long skuId = sku.getLong("sku_id");
                if (skuId == null || skuId <= 0) {
                    continue;
                }
                String outerKey = firstNonBlank(
                        sku.getString("out_sku_sn"),
                        sku.getString("outer_id"),
                        sku.getString("out_sku_id"));
                if (!StringUtils.hasText(outerKey)) {
                    continue;
                }
                out.putIfAbsent(outerKey.trim(), skuId);
            }
        } catch (Exception ignored) {
            // ignore
        }
        return out;
    }

    private static String firstNonBlank(String a, String b, String c) {
        if (StringUtils.hasText(a)) {
            return a.trim();
        }
        if (StringUtils.hasText(b)) {
            return b.trim();
        }
        if (StringUtils.hasText(c)) {
            return c.trim();
        }
        return null;
    }

    /**
     * {@code pdd.goods.spec.id.get} 的表单顶层业务参数（与 POP 常见调用方式一致）。
     */
    public static Map<String, String> specIdGetTopLevelParams(long catId, long parentSpecId, String specName) {
        if (catId <= 0 || parentSpecId <= 0 || !StringUtils.hasText(specName)) {
            throw new IllegalArgumentException("cat_id、parent_spec_id、spec_name 无效");
        }
        Map<String, String> m = new LinkedHashMap<>();
        m.put("cat_id", String.valueOf(catId));
        m.put("parent_spec_id", String.valueOf(parentSpecId));
        m.put("spec_name", specName.trim());
        return m;
    }

    /**
     * 将单根业务 JSON（如 {@link cn.qihangerp.service.external.pdd.PddGoodsAddParamBuilder#buildParamJson} 的输出）
     * 展开为 POP 网关<strong>表单顶层</strong>键值，与官方 {@code pdd.goods.add} curl 一致（各字段与 {@code type}、{@code client_id} 同级，
     * 数组/嵌套对象字段值为 JSON 字符串，<b>不使用</b> {@code param_json}）。
     */
    public static Map<String, String> flattenPopTopLevelFromRootJson(String rootJson) {
        if (!StringUtils.hasText(rootJson)) {
            throw new IllegalArgumentException("rootJson 不能为空");
        }
        JSONObject root = JSON.parseObject(rootJson);
        if (root == null || root.isEmpty()) {
            throw new IllegalArgumentException("rootJson 解析为空对象");
        }
        Map<String, String> out = new LinkedHashMap<>();
        for (String key : root.keySet()) {
            Object v = root.get(key);
            if (v == null) {
                continue;
            }
            String s = toPddTopLevelFormValue(v);
            if (s == null) {
                continue;
            }
            out.put(key, s);
        }
        return out;
    }

    private static String toPddTopLevelFormValue(Object v) {
        if (v instanceof String s) {
            return s;
        }
        if (v instanceof Boolean b) {
            return b ? "true" : "false";
        }
        if (v instanceof Number n) {
            if (v instanceof Double || v instanceof Float) {
                return Double.toString(n.doubleValue());
            }
            if (v instanceof BigDecimal bd) {
                return bd.stripTrailingZeros().toPlainString();
            }
            if (v instanceof BigInteger bi) {
                return bi.toString();
            }
            return Long.toString(n.longValue());
        }
        return JSON.toJSONString(v);
    }

    public static String snippet(String s, int max) {
        if (s == null) {
            return null;
        }
        return s.length() <= max ? s : s.substring(0, max) + "...";
    }

    public static boolean isError(String body) {
        if (!StringUtils.hasText(body)) {
            return true;
        }
        try {
            JSONObject o = JSON.parseObject(body);
            return o != null && o.containsKey("error_response");
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * {@code pdd.goods.information.update}：无 {@code error_response}；若存在 {@code goods_update_response} 则须 {@code is_success=true}。
     */
    public static boolean isInformationUpdateBizOk(String body) {
        if (!StringUtils.hasText(body)) {
            return false;
        }
        try {
            JSONObject root = JSON.parseObject(body);
            if (root == null) {
                return false;
            }
            if (root.containsKey("error_response")) {
                return false;
            }
            JSONObject gr = root.getJSONObject("goods_update_response");
            if (gr != null && !gr.getBooleanValue("is_success")) {
                return false;
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /** 业务失败摘要（含 {@code goods_update_response} 未成功时） */
    public static String summarizeInformationUpdateFailure(String body) {
        try {
            JSONObject root = JSON.parseObject(body);
            if (root == null) {
                return body;
            }
            JSONObject gr = root.getJSONObject("goods_update_response");
            if (gr != null && !gr.getBooleanValue("is_success")) {
                StringBuilder sb = new StringBuilder("goods_update_response.is_success=false");
                if (gr.containsKey("goods_commit_id")) {
                    sb.append(", goods_commit_id=").append(gr.get("goods_commit_id"));
                }
                return sb.toString();
            }
            return formatError(body);
        } catch (Exception e) {
            return body;
        }
    }

    public static String formatError(String body) {
        try {
            JSONObject o = JSON.parseObject(body);
            if (o == null) {
                return body;
            }
            JSONObject err = o.getJSONObject("error_response");
            if (err != null) {
                String sub = err.getString("sub_msg");
                String main = err.getString("error_msg");
                if (StringUtils.hasText(sub)) {
                    return main + " (" + sub + ")";
                }
                return main;
            }
            return body;
        } catch (Exception e) {
            return body;
        }
    }

    /**
     * 解析 {@code pdd.goods.image.upload} 成功体中的 URL（兼容 {@code goods_image_upload_response} / {@code goods_img_upload_response} 等命名）。
     */
    public static String parseGoodsImgUploadUrl(String body) {
        if (!StringUtils.hasText(body)) {
            return null;
        }
        try {
            JSONObject o = JSON.parseObject(body);
            if (o == null || o.containsKey("error_response")) {
                return null;
            }
            String u = extractUrlFromPddImageUploadRoot(o);
            if (StringUtils.hasText(u)) {
                return u.trim();
            }
            JSONObject wrapped = o.getJSONObject("response");
            if (wrapped != null && !wrapped.containsKey("error_response")) {
                u = extractUrlFromPddImageUploadRoot(wrapped);
                if (StringUtils.hasText(u)) {
                    return u.trim();
                }
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    private static String extractUrlFromPddImageUploadRoot(JSONObject o) {
        if (o == null) {
            return null;
        }
        String[] groups = {
                "goods_img_upload_response",
                "goods_image_upload_response",
                "goods_filespace_image_upload_response",
                "filespace_image_upload_response",
                "pdd_goods_image_upload_response",
        };
        for (String g : groups) {
            JSONObject sub = o.getJSONObject(g);
            if (sub == null) {
                continue;
            }
            String u = sub.getString("url");
            if (!StringUtils.hasText(u)) {
                u = sub.getString("image_url");
            }
            if (StringUtils.hasText(u)) {
                return u;
            }
        }
        return null;
    }

    /**
     * 在类目规则 JSON 中解析「销售规格」对应的 {@code parent_spec_id}（取第一个）。
     * <p>兼容：{@code is_sale}/{@code isSale}、{@code parent_spec_id}/{@code parentSpecId}/字符串数值；
     * 若整树未命中，再尝试 {@code goods_properties_rule.properties}、常见 {@code goods_spec_rule} 等数组；
     * 若全树仅出现<strong>一个</strong>不同的 {@code parent_spec_id}，则采用（单规格维度类目）。</p>
     */
    public static Long findFirstSaleParentSpecId(String catRuleBody) {
        if (!StringUtils.hasText(catRuleBody)) {
            return null;
        }
        try {
            JSONObject root = JSON.parseObject(catRuleBody);
            if (root == null) {
                return null;
            }
            JSONObject inner = unwrapCatRulePayload(root);
            if (inner == null) {
                return null;
            }
            long[] holder = new long[]{0L};
            walkForSaleParent(inner, holder);
            if (holder[0] > 0) {
                return holder[0];
            }
            Long fromProps = extractParentSpecFromGoodsPropertiesRule(inner);
            if (fromProps != null && fromProps > 0) {
                return fromProps;
            }
            Long fb = fallbackParentSpecFromSpecRuleArrays(inner);
            if (fb != null && fb > 0) {
                return fb;
            }
            return uniqueParentSpecIdIfSingle(inner);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 当 {@link #findFirstSaleParentSpecId} 为空时，根据类目规则返回体补充说明与处置建议（例如 {@code input_max_spec_num=0} 的图书属性类目）。
     */
    public static String suffixHintForMissingSaleParentSpec(String catRuleBody) {
        if (!StringUtils.hasText(catRuleBody)) {
            return "";
        }
        try {
            JSONObject root = JSON.parseObject(catRuleBody);
            if (root == null) {
                return "";
            }
            JSONObject inner = unwrapCatRulePayload(root);
            if (inner == null) {
                return "";
            }
            JSONObject gpr = inner.getJSONObject("goods_properties_rule");
            if (gpr == null) {
                return "。返回体中无 goods_properties_rule，无法自动解析销售规格；请配置 external.pdd.sku-overrides 或核对类目。";
            }
            int maxSpec = gpr.getIntValue("input_max_spec_num");
            JSONArray props = gpr.getJSONArray("properties");
            boolean anySale = false;
            if (props != null) {
                for (int i = 0; i < props.size(); i++) {
                    Object el = props.get(i);
                    if (el instanceof JSONObject p) {
                        if (isTruthySale(p.get("is_sale")) || isTruthySale(p.get("isSale"))) {
                            anySale = true;
                            break;
                        }
                    }
                }
            }
            StringBuilder sb = new StringBuilder("。");
            if (maxSpec == 0) {
                sb.append(" 拼多多返回 goods_properties_rule.input_max_spec_num=0（不允许商家再增自定义规格维），");
            }
            if (!anySale && props != null && !props.isEmpty()) {
                sb.append(" properties 中无 is_sale=true 的销售规格行（多为书名、ISBN 等普通属性），");
            }
            sb.append("无法通过 pdd.goods.spec.id.get 自动生成 SKU 的 spec_id_list。");
            sb.append(" 请在 external.pdd.sku-overrides 中为每条 SKU 配置 spec-id-list，或改用拼多多侧支持「销售规格」的叶子类目并同步调整 category-map。");
            return sb.toString();
        } catch (Exception e) {
            return "。解析类目规则失败；请配置 external.pdd.sku-overrides 或开启全量日志核对 cat_rule 返回结构。";
        }
    }

    /**
     * 是否可按「无规格」图书类发布：类目规则中 {@code goods_properties_rule.input_max_spec_num==0}（与拼多多无规格发品前提一致）。
     */
    public static boolean isNoSpecBookCatRule(String catRuleBody) {
        if (!StringUtils.hasText(catRuleBody) || isError(catRuleBody)) {
            return false;
        }
        try {
            JSONObject root = JSON.parseObject(catRuleBody);
            JSONObject inner = unwrapCatRulePayload(root);
            if (inner == null) {
                return false;
            }
            JSONObject gpr = inner.getJSONObject("goods_properties_rule");
            if (gpr == null) {
                return false;
            }
            return gpr.getIntValue("input_max_spec_num") == 0;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * POP 成功体多为 {@code goods_cat_rule_get_response}；部分环境为 {@code cat_rule_get_response}（与日志一致）。
     */
    private static JSONObject unwrapCatRulePayload(JSONObject root) {
        if (root == null) {
            return null;
        }
        JSONObject a = root.getJSONObject("goods_cat_rule_get_response");
        if (a != null) {
            return a;
        }
        JSONObject b = root.getJSONObject("cat_rule_get_response");
        if (b != null) {
            return b;
        }
        return root;
    }

    /**
     * 从 {@code goods_properties_rule.properties} 里找销售属性行上的 {@code parent_spec_id}。
     */
    private static Long extractParentSpecFromGoodsPropertiesRule(JSONObject inner) {
        if (inner == null) {
            return null;
        }
        JSONObject gpr = inner.getJSONObject("goods_properties_rule");
        if (gpr == null) {
            return null;
        }
        JSONArray props = gpr.getJSONArray("properties");
        if (props == null || props.isEmpty()) {
            return null;
        }
        for (int i = 0; i < props.size(); i++) {
            Object el = props.get(i);
            if (!(el instanceof JSONObject p)) {
                continue;
            }
            boolean sale = isTruthySale(p.get("is_sale")) || isTruthySale(p.get("isSale"));
            boolean skuRow = p.getBooleanValue("is_sku") || p.getBooleanValue("isSku");
            if (!sale && !skuRow) {
                continue;
            }
            Long pid = readParentSpecId(p);
            if (pid != null && pid > 0) {
                return pid;
            }
            JSONObject spec = p.getJSONObject("spec");
            if (spec != null) {
                pid = readParentSpecId(spec);
                if (pid != null && pid > 0) {
                    return pid;
                }
            }
        }
        return null;
    }

    /**
     * 全树收集 {@code parent_spec_id}，仅当恰好一个不同取值时返回（避免多规格维度误选）。
     */
    private static Long uniqueParentSpecIdIfSingle(JSONObject inner) {
        Set<Long> ids = new LinkedHashSet<>();
        collectAllParentSpecIds(inner, ids);
        if (ids.size() == 1) {
            return ids.iterator().next();
        }
        return null;
    }

    private static void collectAllParentSpecIds(Object node, Set<Long> out) {
        if (node instanceof JSONObject jo) {
            Long p = readParentSpecId(jo);
            if (p != null && p > 0) {
                out.add(p);
            }
            for (String k : jo.keySet()) {
                collectAllParentSpecIds(jo.get(k), out);
            }
        } else if (node instanceof JSONArray ja) {
            for (int i = 0; i < ja.size(); i++) {
                collectAllParentSpecIds(ja.get(i), out);
            }
        }
    }

    private static void walkForSaleParent(Object node, long[] holder) {
        if (holder[0] > 0) {
            return;
        }
        if (node instanceof JSONObject jo) {
            boolean sale = isTruthySale(jo.get("is_sale")) || isTruthySale(jo.get("isSale"));
            if (sale) {
                Long p = readParentSpecId(jo);
                if (p != null && p > 0) {
                    holder[0] = p;
                    return;
                }
            }
            Long pid = readParentSpecId(jo);
            if (pid != null && pid > 0 && (isTruthySale(jo.get("sale")) || isOne(jo.get("spec_type")))) {
                holder[0] = pid;
                return;
            }
            for (String k : jo.keySet()) {
                walkForSaleParent(jo.get(k), holder);
                if (holder[0] > 0) {
                    return;
                }
            }
        } else if (node instanceof JSONArray ja) {
            for (int i = 0; i < ja.size(); i++) {
                walkForSaleParent(ja.get(i), holder);
                if (holder[0] > 0) {
                    return;
                }
            }
        }
    }

    /**
     * 部分类目返回的销售规格列表在固定 key 下，且字段名与递归路径不一致时在此补解析。
     */
    private static Long fallbackParentSpecFromSpecRuleArrays(JSONObject inner) {
        if (inner == null) {
            return null;
        }
        String[] arrayKeys = {
                "goods_spec_rule",
                "goods_spec_rules",
                "spec_rule_list",
                "spec_rules",
                "cat_rule",
        };
        for (String key : arrayKeys) {
            JSONArray arr = findFirstArrayByKey(inner, key);
            if (arr == null || arr.isEmpty()) {
                continue;
            }
            for (int i = 0; i < arr.size(); i++) {
                Object el = arr.get(i);
                if (!(el instanceof JSONObject row)) {
                    continue;
                }
                Long p = readParentSpecId(row);
                if (p == null || p <= 0) {
                    continue;
                }
                if (isTruthySale(row.get("is_sale")) || isTruthySale(row.get("isSale"))) {
                    return p;
                }
            }
            JSONObject first = arr.getJSONObject(0);
            if (first != null) {
                Long p = readParentSpecId(first);
                if (p != null && p > 0 && arr.size() == 1) {
                    return p;
                }
            }
        }
        return null;
    }

    private static JSONArray findFirstArrayByKey(JSONObject root, String key) {
        if (root == null || !StringUtils.hasText(key)) {
            return null;
        }
        JSONArray direct = root.getJSONArray(key);
        if (direct != null && !direct.isEmpty()) {
            return direct;
        }
        for (String k : root.keySet()) {
            Object v = root.get(k);
            if (v instanceof JSONObject child) {
                JSONArray nested = findFirstArrayByKey(child, key);
                if (nested != null && !nested.isEmpty()) {
                    return nested;
                }
            }
        }
        return null;
    }

    private static Long readParentSpecId(JSONObject jo) {
        if (jo == null) {
            return null;
        }
        Long p = jo.getLong("parent_spec_id");
        if (p != null && p > 0) {
            return p;
        }
        p = jo.getLong("parentSpecId");
        if (p != null && p > 0) {
            return p;
        }
        String s = jo.getString("parent_spec_id");
        if (!StringUtils.hasText(s)) {
            s = jo.getString("parentSpecId");
        }
        if (StringUtils.hasText(s)) {
            try {
                long v = Long.parseLong(s.trim());
                return v > 0 ? v : null;
            } catch (NumberFormatException ignored) {
                return null;
            }
        }
        return null;
    }

    private static boolean isOne(Object v) {
        if (v == null) {
            return false;
        }
        if (v instanceof Number n) {
            return n.intValue() == 1;
        }
        return "1".equals(String.valueOf(v).trim());
    }

    private static boolean isTruthySale(Object v) {
        if (v == null) {
            return false;
        }
        if (v instanceof Boolean b) {
            return b;
        }
        if (v instanceof Number n) {
            return n.intValue() == 1;
        }
        return "1".equals(String.valueOf(v)) || "true".equalsIgnoreCase(String.valueOf(v));
    }

    public static Long parseSpecIdFromSpecIdGetResponse(String body) {
        if (!StringUtils.hasText(body)) {
            return null;
        }
        try {
            JSONObject o = JSON.parseObject(body);
            if (o == null) {
                return null;
            }
            JSONObject r = o.getJSONObject("goods_spec_id_get_response");
            if (r == null) {
                r = o.getJSONObject("spec_id_get_response");
            }
            if (r == null) {
                return null;
            }
            Long id = r.getLong("spec_id");
            if (id != null && id > 0) {
                return id;
            }
            return null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * {@code goods_properties_rule.properties} 中「商品级」必填项（{@code required} 为真且非 {@code is_sku}），
     * 用于 {@code pdd.goods.add} 根级 {@code goods_properties} 自动补全。
     */
    public static List<CatGoodsPropertyRuleRow> listRequiredGoodsLevelPropertyRules(String catRuleBody) {
        if (!StringUtils.hasText(catRuleBody) || isError(catRuleBody)) {
            return List.of();
        }
        try {
            JSONObject root = JSON.parseObject(catRuleBody);
            if (root == null) {
                return List.of();
            }
            JSONObject inner = unwrapCatRulePayload(root);
            if (inner == null) {
                return List.of();
            }
            JSONObject gpr = inner.getJSONObject("goods_properties_rule");
            if (gpr == null) {
                return List.of();
            }
            JSONArray props = gpr.getJSONArray("properties");
            if (props == null || props.isEmpty()) {
                return List.of();
            }
            List<CatGoodsPropertyRuleRow> out = new ArrayList<>();
            for (int i = 0; i < props.size(); i++) {
                Object el = props.get(i);
                if (!(el instanceof JSONObject p)) {
                    continue;
                }
                if (!isTruthyRequired(p.get("required"))) {
                    continue;
                }
                if (p.getBooleanValue("is_sku") || p.getBooleanValue("isSku")) {
                    continue;
                }
                long refPid = p.getLongValue("ref_pid");
                if (refPid <= 0) {
                    refPid = p.getLongValue("refPid");
                }
                if (refPid <= 0) {
                    continue;
                }
                String name = p.getString("name");
                JSONArray values = p.getJSONArray("values");
                if (values == null || values.isEmpty()) {
                    values = p.getJSONArray("property_values");
                }
                String maxVal = p.getString("max_value");
                if (!StringUtils.hasText(maxVal)) {
                    maxVal = p.getString("maxValue");
                }
                out.add(new CatGoodsPropertyRuleRow(refPid, name, values, maxVal));
            }
            return out.isEmpty() ? List.of() : Collections.unmodifiableList(out);
        } catch (Exception e) {
            return List.of();
        }
    }

    /** {@code pdd.goods.cat.rule.get} 成功体在日志中的加长片段上限（字符） */
    public static final int CAT_RULE_GET_LOG_BODY_SNIPPET_MAX = 12000;

    /**
     * 将 {@code pdd.goods.cat.rule.get} 响应压缩为一行摘要，便于检索与对照发品逻辑（不含完整 JSON）。
     */
    public static String summarizeCatRuleGetForLog(String catRuleBody) {
        if (!StringUtils.hasText(catRuleBody)) {
            return "(empty)";
        }
        if (isError(catRuleBody)) {
            return "ERROR " + formatError(catRuleBody);
        }
        try {
            JSONObject root = JSON.parseObject(catRuleBody);
            if (root == null) {
                return "(parse root null)";
            }
            JSONObject inner = unwrapCatRulePayload(root);
            if (inner == null) {
                return "(no cat_rule_get_response/goods_cat_rule_get_response)";
            }
            StringBuilder sb = new StringBuilder(256);
            JSONObject gpr = inner.getJSONObject("goods_properties_rule");
            if (gpr != null) {
                sb.append("input_max_spec_num=").append(gpr.getIntValue("input_max_spec_num"));
                JSONArray props = gpr.getJSONArray("properties");
                int n = props == null ? 0 : props.size();
                sb.append(" goodsPropTotal=").append(n);
                int reqSku = 0;
                int reqGoods = 0;
                if (props != null) {
                    for (int i = 0; i < props.size(); i++) {
                        Object el = props.get(i);
                        if (!(el instanceof JSONObject p)) {
                            continue;
                        }
                        if (!isTruthyRequired(p.get("required"))) {
                            continue;
                        }
                        boolean sku = p.getBooleanValue("is_sku") || p.getBooleanValue("isSku");
                        if (sku) {
                            reqSku++;
                        } else {
                            reqGoods++;
                        }
                    }
                }
                sb.append(" requiredNonSku=").append(reqGoods).append(" requiredSku=").append(reqSku);
            } else {
                sb.append("goods_properties_rule=(absent)");
            }
            List<CatGoodsPropertyRuleRow> reqGoodsRows = listRequiredGoodsLevelPropertyRules(catRuleBody);
            if (!reqGoodsRows.isEmpty()) {
                sb.append(" requiredGoodsProps=[");
                for (int i = 0; i < reqGoodsRows.size(); i++) {
                    if (i > 0) {
                        sb.append(';');
                    }
                    CatGoodsPropertyRuleRow r = reqGoodsRows.get(i);
                    sb.append(r.getRefPid()).append(':');
                    sb.append(r.getName() == null ? "" : r.getName());
                }
                sb.append(']');
            }
            Long saleParent = findFirstSaleParentSpecId(catRuleBody);
            sb.append(" saleParentSpecId=").append(saleParent != null && saleParent > 0 ? saleParent : "none");
            return sb.toString();
        } catch (Exception e) {
            return "(summarizeFail " + e.getClass().getSimpleName() + ": " + e.getMessage() + ")";
        }
    }

    private static boolean isTruthyRequired(Object v) {
        if (v == null) {
            return false;
        }
        if (v instanceof Boolean b) {
            return b;
        }
        if (v instanceof Number n) {
            return n.intValue() == 1;
        }
        String s = String.valueOf(v).trim();
        return "1".equals(s) || "true".equalsIgnoreCase(s);
    }

    /**
     * 一条商品级类目属性规则（来自 {@code pdd.goods.cat.rule.get}）。
     */
    public static final class CatGoodsPropertyRuleRow {
        private final long refPid;
        private final String name;
        /** 可选值列表（非空时表示需从枚举中选，元素多为含 {@code vid}/{@code value} 的对象） */
        private final JSONArray valueOptions;
        /** 文本最大长度提示（平台返回的字符串，多为数字） */
        private final String maxValueHint;

        public CatGoodsPropertyRuleRow(long refPid, String name, JSONArray valueOptions, String maxValueHint) {
            this.refPid = refPid;
            this.name = name;
            this.valueOptions = valueOptions;
            this.maxValueHint = maxValueHint;
        }

        public long getRefPid() {
            return refPid;
        }

        public String getName() {
            return name;
        }

        public JSONArray getValueOptions() {
            return valueOptions;
        }

        public String getMaxValueHint() {
            return maxValueHint;
        }
    }
}
