package cn.qihangerp.service.external.sph;

import cn.qihangerp.model.entity.OGoods;
import cn.qihangerp.model.entity.OGoodsSku;
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
import cn.qihangerp.model.vo.SphPublishLaneResultVo;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * 微信视频号小店发品：先上传素材换 {@code img_url}，再调 {@code channels.ec.product.add/update}。
 * <p>凭证来自 {@link ExternalGoodsUpsertRequest#getSphPopAuth()}；与拼多多 {@link cn.qihangerp.service.external.pdd.ExternalPddPublishService} 对称。</p>
 *
 * @author guochengyu
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExternalSphPublishService {

    private static final String NO_BRAND_ID = "2100000000";

    private final ExternalSphProperties props;
    private final WeixinShopEcImageUploadService imageUploadService;
    private final WeixinShopEcHttpClient httpClient;

    /**
     * 落库成功后尝试微信侧发品；失败返回 {@code success=false}（不抛异常），由编排层决定是否阻断。
     */
    public SphPublishLaneResultVo publish(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req) {
        if (!props.isPublishEnabled()) {
            log.info("[SPH] publish skipped shopId={} outGoodsId={} reason=publish-disabled",
                    req != null ? req.getShopId() : null,
                    req != null ? req.getOutGoodsId() : null);
            return SphPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(null)
                    .message("external.sph.publish-enabled=false，未调用微信视频号小店")
                    .build();
        }
        if (req == null) {
            return SphPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("请求参数不能为空")
                    .build();
        }
        ExternalGoodsUpsertRequest.SphPopAuth auth = req.getSphPopAuth();
        if (auth == null || !StringUtils.hasText(auth.getAppKey()) || !StringUtils.hasText(auth.getAppSecret())
                || !StringUtils.hasText(auth.getAccessToken())) {
            log.info("[SPH] publish skipped shopId={} outGoodsId={} reason=sphPopAuth-incomplete",
                    req.getShopId(), req.getOutGoodsId());
            return SphPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("微信小店凭证不完整：请在请求体 sphPopAuth 中传入 appKey、appSecret、accessToken")
                    .build();
        }
        String token = auth.getAccessToken().trim();
        boolean update = req.getSphProductId() != null && req.getSphProductId() > 0;
        String lane = update ? "PRODUCT_UPDATE" : "PRODUCT_ADD";
        try {
            HeadAndDetailWxImages imgs = buildWxImages(token, req);
            JSONObject payload = buildProductPayload(goods, skus, req, imgs, update, token);
            String url = (update ? props.getProductUpdateUrl() : props.getProductAddUrl());
            if (!StringUtils.hasText(url)) {
                return failVo(true, lane, "external.sph.product-add-url / product-update-url 未配置");
            }
            String fullUrl = url + (url.contains("?") ? "&" : "?")
                    + "access_token=" + URLEncoder.encode(token, StandardCharsets.UTF_8);
            Duration readTimeout = Duration.ofSeconds(Math.max(5, props.getHttpReadTimeoutSeconds()));
            String raw = httpClient.postJson(fullUrl, payload.toJSONString(), readTimeout);
            if (!WeixinShopEcHttpClient.isBizOk(raw)) {
                log.warn("[SPH] {} fail shopId={} outGoodsId={} err={} snippet={}",
                        lane, req.getShopId(), req.getOutGoodsId(),
                        WeixinShopEcHttpClient.formatWxError(raw),
                        WeixinShopEcLogSupport.snippet(raw, 800));
                return SphPublishLaneResultVo.builder()
                        .attempted(true)
                        .success(false)
                        .message(lane + " 失败: " + WeixinShopEcHttpClient.formatWxError(raw))
                        .responseSnippet(WeixinShopEcLogSupport.snippet(raw, 2000))
                        .sphProductId(null)
                        .build();
            }
            String productIdStr = parseProductIdString(raw);
            if (!StringUtils.hasText(productIdStr) && !update) {
                return SphPublishLaneResultVo.builder()
                        .attempted(true)
                        .success(false)
                        .message(lane + " 成功但未解析到 data.product_id")
                        .responseSnippet(WeixinShopEcLogSupport.snippet(raw, 2000))
                        .sphProductId(null)
                        .build();
            }
            String outPid = StringUtils.hasText(productIdStr) ? productIdStr : String.valueOf(req.getSphProductId());
            log.info("[SPH] {} ok shopId={} outGoodsId={} sphProductId={} snippet={}",
                    lane, req.getShopId(), req.getOutGoodsId(), outPid,
                    WeixinShopEcLogSupport.snippet(raw, 500));
            return SphPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(true)
                    .message(lane + " 成功")
                    .responseSnippet(WeixinShopEcLogSupport.snippet(raw, 2000))
                    .sphProductId(outPid)
                    .build();
        } catch (Exception e) {
            log.warn("[SPH] publish exception shopId={} outGoodsId={} lane={} err={}",
                    req.getShopId(), req.getOutGoodsId(), lane, e.getMessage(), e);
            return SphPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(false)
                    .message(lane + " 异常: " + e.getMessage())
                    .responseSnippet(null)
                    .sphProductId(null)
                    .build();
        }
    }

    private static SphPublishLaneResultVo failVo(boolean attempted, String lane, String msg) {
        return SphPublishLaneResultVo.builder()
                .attempted(attempted)
                .success(false)
                .message(msg)
                .responseSnippet(null)
                .sphProductId(null)
                .build();
    }

    private HeadAndDetailWxImages buildWxImages(String token, ExternalGoodsUpsertRequest req) throws Exception {
        int minHead = Math.max(1, props.getMinHeadImages());
        List<String> headSources = new ArrayList<>();
        LinkedHashSet<String> seen = new LinkedHashSet<>();
        if (StringUtils.hasText(req.getMainImage())) {
            addUnique(headSources, seen, req.getMainImage().trim());
        }
        if (!CollectionUtils.isEmpty(req.getImages())) {
            for (String u : req.getImages()) {
                addUnique(headSources, seen, u);
            }
        }
        List<String> headWx = new ArrayList<>();
        for (String s : headSources) {
            headWx.add(imageUploadService.ensureImgUrl(token, s));
            if (headWx.size() >= minHead) {
                break;
            }
        }
        if (headWx.size() < minHead && !CollectionUtils.isEmpty(req.getDetailImages())) {
            for (String u : req.getDetailImages()) {
                if (!StringUtils.hasText(u)) {
                    continue;
                }
                String t = u.trim();
                if (!seen.add(t)) {
                    continue;
                }
                headWx.add(imageUploadService.ensureImgUrl(token, t));
                if (headWx.size() >= minHead) {
                    break;
                }
            }
        }
        if (headWx.size() < minHead) {
            throw new IllegalStateException("微信小店主图至少需要 " + minHead + " 张不同素材（上传后仍不足），请补充 mainImage/images/detailImages");
        }

        List<String> detailSources = new ArrayList<>();
        LinkedHashSet<String> dSeen = new LinkedHashSet<>();
        if (!CollectionUtils.isEmpty(req.getDetailImages())) {
            for (String u : req.getDetailImages()) {
                if (!StringUtils.hasText(u)) {
                    continue;
                }
                String t = u.trim();
                if (!dSeen.add(t)) {
                    continue;
                }
                detailSources.add(t);
            }
        }
        if (detailSources.isEmpty() && !headSources.isEmpty()) {
            detailSources.add(headSources.get(0));
        }
        List<String> detailWx = imageUploadService.uploadDistinctInOrder(token, detailSources);
        if (detailWx.isEmpty()) {
            detailWx = List.of(headWx.get(0));
        }
        return new HeadAndDetailWxImages(headWx, detailWx);
    }

    private static void addUnique(List<String> out, Set<String> seen, String u) {
        if (!StringUtils.hasText(u)) {
            return;
        }
        String t = u.trim();
        if (seen.add(t)) {
            out.add(t);
        }
    }

    private JSONObject buildProductPayload(OGoods goods, List<OGoodsSku> skuRows, ExternalGoodsUpsertRequest req,
                                           HeadAndDetailWxImages imgs, boolean update, String accessToken) throws Exception {
        JSONObject root = new JSONObject();
        if (update) {
            root.put("product_id", req.getSphProductId());
        }
        root.put("title", normalizeTitle(req, goods));
        if (StringUtils.hasText(req.getOutGoodsId())) {
            String outPid = req.getOutGoodsId().trim();
            if (outPid.length() <= 128) {
                root.put("out_product_id", outPid);
            }
        }
        JSONArray head = new JSONArray();
        for (String u : imgs.headImgUrls()) {
            head.add(u);
        }
        root.put("head_imgs", head);
        root.put("deliver_method", 0);
        root.put("deliver_acct_type", new JSONArray());
        JSONArray catsV2 = resolveCatsV2OrThrow(req, imgs.headImgUrls(), accessToken);
        root.put("cats_v2", catsV2);
        JSONObject extra = new JSONObject();
        extra.put("seven_day_return", 0);
        extra.put("freight_insurance", 0);
        root.put("extra_service", extra);
        JSONObject desc = new JSONObject();
        JSONArray dimgs = new JSONArray();
        for (String u : imgs.detailImgUrls()) {
            dimgs.add(u);
        }
        desc.put("imgs", dimgs);
        root.put("desc_info", desc);
        if (StringUtils.hasText(props.getExpressTemplateId())) {
            JSONObject express = new JSONObject();
            express.put("template_id", props.getExpressTemplateId().trim());
            root.put("express_info", express);
        }
        root.put("brand_id", NO_BRAND_ID);
        root.put("listing", props.getListingOnSave());
        String token = req.getSphPopAuth() != null && StringUtils.hasText(req.getSphPopAuth().getAccessToken())
                ? req.getSphPopAuth().getAccessToken().trim() : "";
        JSONArray skuArr = buildSkus(skuRows, req, imgs.headImgUrls().get(0), token);
        if (skuArr.isEmpty()) {
            throw new IllegalStateException("无可发品 SKU");
        }
        root.put("skus", skuArr);
        return root;
    }

    private JSONArray resolveCatsV2OrThrow(ExternalGoodsUpsertRequest req, List<String> headImgUrls, String accessToken) {
        if (!StringUtils.hasText(props.getProductClassifyUrl())) {
            throw new IllegalStateException("external.sph.product-classify-url 未配置，无法获取推荐类目");
        }
        if (!StringUtils.hasText(accessToken)) {
            throw new IllegalStateException("缺少 accessToken，无法调用类目推荐接口");
        }
        if (CollectionUtils.isEmpty(headImgUrls)) {
            throw new IllegalStateException("缺少主图，无法调用类目推荐接口");
        }
        try {
            JSONObject body = new JSONObject();
            body.put("req_type", 1);
            if (StringUtils.hasText(req.getTitle())) {
                body.put("title", req.getTitle().trim());
            }
            JSONArray head = new JSONArray();
            for (String u : headImgUrls) {
                if (StringUtils.hasText(u)) {
                    head.add(u);
                }
            }
            body.put("head_imgs", head);
            String url = props.getProductClassifyUrl();
            String fullUrl = url + (url.contains("?") ? "&" : "?")
                    + "access_token=" + URLEncoder.encode(accessToken.trim(), StandardCharsets.UTF_8);
            String raw = httpClient.postJson(fullUrl, body.toJSONString(),
                    Duration.ofSeconds(Math.max(5, props.getHttpReadTimeoutSeconds())));
            log.info("[SPH] classify response shopId={} outGoodsId={} snippet={}",
                    req.getShopId(), req.getOutGoodsId(), WeixinShopEcLogSupport.snippet(raw, 2000));
            if (!WeixinShopEcHttpClient.isBizOk(raw)) {
                throw new IllegalStateException("类目推荐失败: " + WeixinShopEcHttpClient.formatWxError(raw));
            }
            JSONArray catsV2 = parseRecommendedCatsV2(raw);
            if (catsV2 == null || catsV2.isEmpty()) {
                throw new IllegalStateException("类目推荐成功但未返回可用类目链");
            }
            String leafCat = catsV2.getJSONObject(catsV2.size() - 1).getString("cat_id");
            log.info("[SPH] classify ok shopId={} outGoodsId={} catsV2Size={} leafCatId={} catsV2={}",
                    req.getShopId(), req.getOutGoodsId(), catsV2.size(), leafCat, catsV2);
            return catsV2;
        } catch (IllegalStateException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException("类目推荐异常: " + e.getMessage(), e);
        }
    }

    private static JSONArray parseRecommendedCatsV2(String raw) {
        JSONObject root = JSON.parseObject(raw);
        JSONObject data = root.getJSONObject("data");
        if (data != null) {
            JSONArray classifyList = data.getJSONArray("classify_list");
            JSONArray fromClassify = parseFromClassifyTree(classifyList);
            if (fromClassify != null && !fromClassify.isEmpty()) {
                return fromClassify;
            }
        }
        JSONArray categories = root.getJSONArray("categories");
        if (categories == null || categories.isEmpty()) {
            return null;
        }
        for (Object obj : categories) {
            if (!(obj instanceof JSONObject item)) {
                continue;
            }
            JSONObject cats = item.getJSONObject("cats");
            JSONArray catInfo = cats == null ? null : cats.getJSONArray("cat_info");
            if (catInfo == null || catInfo.isEmpty()) {
                continue;
            }
            if (hasCategoryAuth(item) || hasCategoryAuth(cats)) {
                JSONArray out = new JSONArray();
                for (Object c : catInfo) {
                    if (!(c instanceof JSONObject cObj)) {
                        continue;
                    }
                    String catId = cObj.getString("cat_id");
                    if (!StringUtils.hasText(catId) && cObj.containsKey("cat_id")) {
                        Object n = cObj.get("cat_id");
                        catId = n == null ? null : String.valueOf(n);
                    }
                    if (!StringUtils.hasText(catId)) {
                        continue;
                    }
                    JSONObject one = new JSONObject();
                    String catIdTrim = catId == null ? "" : catId.trim();
                    if (!StringUtils.hasText(catIdTrim)) {
                        continue;
                    }
                    one.put("cat_id", catIdTrim);
                    out.add(one);
                }
                if (!out.isEmpty()) {
                    return out;
                }
            }
        }
        return null;
    }

    private static JSONArray parseFromClassifyTree(JSONArray classifyList) {
        if (classifyList == null || classifyList.isEmpty()) {
            return null;
        }
        JSONObject node = pickBestClassifyNode(classifyList);
        if (node == null) {
            return null;
        }
        JSONArray out = new JSONArray();
        while (node != null) {
            Long catIdNum = node.getLong("cat_id");
            String catId = catIdNum == null ? node.getString("cat_id") : String.valueOf(catIdNum);
            if (StringUtils.hasText(catId)) {
                JSONObject one = new JSONObject();
                one.put("cat_id", catId.trim());
                out.add(one);
            }
            JSONArray sub = node.getJSONArray("sub_classify");
            if (sub == null || sub.isEmpty()) {
                break;
            }
            node = pickBestClassifyNode(sub);
        }
        return out.isEmpty() ? null : out;
    }

    private static JSONObject pickBestClassifyNode(JSONArray arr) {
        JSONObject bestQualified = null;
        double bestQualifiedScore = -1D;
        JSONObject bestAny = null;
        double bestAnyScore = -1D;
        for (Object o : arr) {
            if (!(o instanceof JSONObject n)) {
                continue;
            }
            double score = n.getDoubleValue("confidence");
            if (bestAny == null || score > bestAnyScore) {
                bestAny = n;
                bestAnyScore = score;
            }
            if (n.getBooleanValue("qualification")) {
                if (bestQualified == null || score > bestQualifiedScore) {
                    bestQualified = n;
                    bestQualifiedScore = score;
                }
            }
        }
        return bestQualified != null ? bestQualified : bestAny;
    }

    private static boolean hasCategoryAuth(JSONObject o) {
        if (o == null) {
            return false;
        }
        if (o.containsKey("has_auth")) {
            return o.getBooleanValue("has_auth");
        }
        if (o.containsKey("has_permission")) {
            return o.getBooleanValue("has_permission");
        }
        if (o.containsKey("can_publish")) {
            return o.getBooleanValue("can_publish");
        }
        return false;
    }

    private JSONArray buildSkus(List<OGoodsSku> skuRows, ExternalGoodsUpsertRequest req, String defaultThumbWxUrl,
                                String accessToken) {
        JSONArray arr = new JSONArray();
        List<OGoodsSku> rows = skuRows == null ? List.of() : skuRows;
        for (OGoodsSku row : rows) {
            if (row == null || !StringUtils.hasText(row.getOuterErpSkuId())) {
                continue;
            }
            try {
                JSONObject sku = new JSONObject();
                String outSku = row.getOuterErpSkuId().trim();
                if (outSku.length() <= 128) {
                    sku.put("out_sku_id", outSku);
                }
                if (StringUtils.hasText(row.getSkuCode())) {
                    sku.put("sku_code", truncate(row.getSkuCode().trim(), 100));
                }
                BigDecimal priceYuan = resolveSalePriceYuan(req, outSku);
                if (priceYuan != null) {
                    long fen = priceYuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).longValue();
                    sku.put("sale_price", fen);
                }
                int stock = resolveStock(req, outSku);
                sku.put("stock_num", stock);
                String srcThumb = findSkuImageSource(req, outSku);
                String thumbWx = defaultThumbWxUrl;
                if (StringUtils.hasText(srcThumb) && StringUtils.hasText(accessToken)) {
                    thumbWx = imageUploadService.ensureImgUrl(accessToken, srcThumb);
                }
                sku.put("thumb_img", thumbWx);
                JSONArray skuAttrs = new JSONArray();
                JSONObject one = new JSONObject();
                one.put("attr_key", "规格");
                one.put("attr_value", truncate(StringUtils.hasText(row.getSkuName()) ? row.getSkuName() : outSku, 40));
                skuAttrs.add(one);
                sku.put("sku_attrs", skuAttrs);
                arr.add(sku);
            } catch (Exception e) {
                throw new IllegalStateException("组装微信 SKU 失败 outSkuId=" + row.getOuterErpSkuId() + ": " + e.getMessage(), e);
            }
        }
        return arr;
    }

    private BigDecimal resolveSalePriceYuan(ExternalGoodsUpsertRequest req, String outSkuId) {
        if (req.getSkus() == null) {
            return null;
        }
        for (ExternalGoodsUpsertRequest.Sku s : req.getSkus()) {
            if (s == null || !outSkuId.equals(s.getOutSkuId())) {
                continue;
            }
            if (s.getSalePrice() != null) {
                return s.getSalePrice();
            }
            if (s.getRetailPrice() != null) {
                return s.getRetailPrice();
            }
        }
        return null;
    }

    private int resolveStock(ExternalGoodsUpsertRequest req, String outSkuId) {
        if (req.getSkus() == null) {
            return 0;
        }
        for (ExternalGoodsUpsertRequest.Sku s : req.getSkus()) {
            if (s == null || !outSkuId.equals(s.getOutSkuId())) {
                continue;
            }
            if (s.getStockQty() == null) {
                return 0;
            }
            return Math.max(0, s.getStockQty());
        }
        return 0;
    }

    private String findSkuImageSource(ExternalGoodsUpsertRequest req, String outSkuId) {
        if (req.getSkus() == null) {
            return null;
        }
        for (ExternalGoodsUpsertRequest.Sku s : req.getSkus()) {
            if (s != null && outSkuId.equals(s.getOutSkuId()) && StringUtils.hasText(s.getImageUrl())) {
                return s.getImageUrl().trim();
            }
        }
        return null;
    }

    private String normalizeTitle(ExternalGoodsUpsertRequest req, OGoods goods) {
        String t = StringUtils.hasText(req.getTitle()) ? req.getTitle().trim() : "";
        if (!StringUtils.hasText(t) && goods != null && StringUtils.hasText(goods.getName())) {
            t = goods.getName().trim();
        }
        if (!StringUtils.hasText(t)) {
            t = "商品";
        }
        while (t.length() < 5) {
            t = t + "　";
        }
        return truncate(t, 60);
    }

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

    private static String parseProductIdString(String raw) {
        try {
            JSONObject o = JSON.parseObject(raw);
            JSONObject data = o.getJSONObject("data");
            if (data == null) {
                return null;
            }
            if (data.get("product_id") == null) {
                return null;
            }
            return data.get("product_id").toString();
        } catch (Exception e) {
            return null;
        }
    }

    private record HeadAndDetailWxImages(List<String> headImgUrls, List<String> detailImgUrls) {
    }
}
