package cn.qihangerp.service.external.pdd;

import cn.qihangerp.model.entity.OGoods;
import cn.qihangerp.model.entity.OGoodsSku;
import cn.qihangerp.model.request.ExternalGoodsDelistRequest;
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
import cn.qihangerp.model.vo.PddPublishLaneResultVo;
import cn.qihangerp.service.external.shop.PddShopCredential;
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.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 拼多多发布：POP 凭证仅来自本次请求的 {@link ExternalGoodsUpsertRequest#getPddPopAuth()}；不依赖 {@code o_shop}。
 * 可选自动拉取类目规则与 spec.id；图书等 {@code input_max_spec_num=0} 类目可走无规格单 SKU（{@code sku_list} 与 POP 文档一致：{@code spec_id_list} 字符串 {@code "[]"}，{@code price}/{@code multi_price} 分数字符串）。
 *
 * @author guochengyu
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExternalPddPublishService {

    private final ExternalPddProperties props;
    private final PddGoodsAddParamBuilder paramBuilder;
    private final PddPopClient pddPopClient;
    private final PddCatRuleSpecAutoResolver catRuleSpecAutoResolver;
    private final PddGoodsImageRehostService pddGoodsImageRehostService;

    /**
     * 落库成功后尝试拼多多上架；返回链路诊断信息。
     */
    public PddPublishLaneResultVo publish(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req) {
        if (!props.isPublishEnabled()) {
            log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=publish-disabled",
                    req != null ? req.getShopId() : null,
                    req != null ? req.getOutGoodsId() : null);
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(null)
                    .message("external.pdd.publish-enabled=false，未调用拼多多")
                    .build();
        }

        if (req == null) {
            log.info("[PDD] publish skipped shopId=null outGoodsId=null reason=req-null");
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("请求参数不能为空")
                    .shopCredentialSource("NONE")
                    .build();
        }

        PddShopCredential cred = resolveCredentialFromRequest(req);
        if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret())
                || !StringUtils.hasText(cred.getAccessToken())) {
            log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=pddPopAuth-incomplete",
                    req.getShopId(), req.getOutGoodsId());
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("拼多多凭证不完整：请在请求体 pddPopAuth 中传入 appKey、appSecret、accessToken")
                    .shopCredentialSource("NONE")
                    .build();
        }

        if (log.isDebugEnabled()) {
            log.debug("PDD publish shopId={} appKey={}", req.getShopId(), PddSensitiveLogUtil.maskForLog(cred.getAppKey()));
        }

        String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl();

        List<OGoodsSku> skuRows = skus == null ? List.of() : skus.stream()
                .filter(s -> s != null && StringUtils.hasText(s.getOuterErpSkuId()))
                .toList();

        Long catId = paramBuilder.resolveCatIdForPublish(req.getCategoryCode(), props);
        if (catId == null || catId <= 0) {
            log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=category-map-missing categoryCode={}",
                    req.getShopId(), req.getOutGoodsId(), req.getCategoryCode());
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("缺少拼多多类目映射：external.pdd.category-map 未配置 categoryCode=" + req.getCategoryCode() + " 或 DEFAULT")
                    .shopCredentialSource(cred.getSource())
                    .build();
        }

        String catRuleSnippet = null;
        boolean catFetched = false;
        boolean specAuto = false;
        String autoDetail = null;
        boolean noSpecBookPublish = false;

        List<ExternalPddProperties.PddSkuOverride> effectiveOverrides = null;
        PddCatRuleSpecAutoResolver.AutoResolveResult resolveResult = null;
        if (!CollectionUtils.isEmpty(props.getSkuOverrides())) {
            effectiveOverrides = props.getSkuOverrides();
        } else if (props.isAutoResolveSpecIdsWhenSkuOverridesEmpty()) {
            resolveResult = catRuleSpecAutoResolver.resolve(cred, gateway, req, skuRows, catId);
            catRuleSnippet = resolveResult.getCatRuleSnippet();
            catFetched = resolveResult.isCatRuleFetched();
            autoDetail = resolveResult.getDetail();
            if (resolveResult.isResolved() && resolveResult.getOverrides() != null && resolveResult.getOverrides().size() == skuRows.size()) {
                effectiveOverrides = resolveResult.getOverrides();
                specAuto = true;
            } else if (PddOpenApiSupport.isNoSpecBookCatRule(resolveResult.getCatRuleRaw()) && skuRows.size() == 1) {
                noSpecBookPublish = true;
                autoDetail = "类目 input_max_spec_num=0：拼多多无规格发品（单 sku_list，spec_id_list=\"[]\" 字符串，price/multi_price 分数字符串，未调用 spec.id.get）";
                log.info("[PDD] no-spec book publish shopId={} outGoodsId={}", req.getShopId(), req.getOutGoodsId());
            } else if (PddOpenApiSupport.isNoSpecBookCatRule(resolveResult.getCatRuleRaw())) {
                log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=no-spec-requires-single-sku skuCount={}",
                        req.getShopId(), req.getOutGoodsId(), skuRows.size());
                return PddPublishLaneResultVo.builder()
                        .attempted(false)
                        .success(false)
                        .message("无规格类目发布仅支持 1 条 SKU，当前 " + skuRows.size()
                                + " 条；请合并为单 SKU 或配置 external.pdd.sku-overrides / 使用有销售规格的类目")
                        .shopCredentialSource(cred.getSource())
                        .catRuleFetched(catFetched)
                        .catRuleSnippet(catRuleSnippet)
                        .specAutoResolved(false)
                        .autoResolveDetail(autoDetail)
                        .build();
            } else {
                log.info("[PDD] publish skipped shopId={} outGoodsId={} reason=spec-auto-resolve-fail detail={}",
                        req.getShopId(), req.getOutGoodsId(), autoDetail);
                return PddPublishLaneResultVo.builder()
                        .attempted(false)
                        .success(false)
                        .message("自动解析 SKU 规格失败：" + (autoDetail == null ? "unknown" : autoDetail))
                        .shopCredentialSource(cred.getSource())
                        .catRuleFetched(catFetched)
                        .catRuleSnippet(catRuleSnippet)
                        .specAutoResolved(false)
                        .autoResolveDetail(autoDetail)
                        .build();
            }
        }

        String autoFetchedCatRuleRaw = null;
        if (props.isAutoFetchCatRule() && !catFetched) {
            try {
                autoFetchedCatRuleRaw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
                        "pdd.goods.cat.rule.get", PddOpenApiSupport.catRuleGetTopLevelParams(catId));
                catRuleSnippet = PddOpenApiSupport.snippet(autoFetchedCatRuleRaw, 2000);
                catFetched = true;
            } catch (Exception e) {
                log.warn("pdd.goods.cat.rule.get shopId={} outGoodsId={} failed: {}",
                        req.getShopId(), req.getOutGoodsId(), e.getMessage());
                catRuleSnippet = "ERROR: " + e.getMessage();
                catFetched = true;
            }
        }

        String catRuleRawForGoodsProps = resolveCatRuleRawForGoodsProperties(
                cred, gateway, catId, resolveResult, autoFetchedCatRuleRaw, props, req);

        try {
            String goodsAddRootJson = noSpecBookPublish
                    ? paramBuilder.buildParamJsonNoSpecBook(goods, skus, req, props, catRuleRawForGoodsProps)
                    : paramBuilder.buildParamJson(goods, skus, req, props, effectiveOverrides, catRuleRawForGoodsProps);
            goodsAddRootJson = pddGoodsImageRehostService.rehostExternalImagesInGoodsAddJson(
                    goodsAddRootJson, gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken());
            boolean useInfoUpdate = req.getPddGoodsId() != null && req.getPddGoodsId() > 0;
            String raw;
            String lane;
            String okMsg;
            String failPrefix;
            if (useInfoUpdate) {
                log.info("[PDD] pdd.goods.information.update begin shopId={} outGoodsId={} pddGoodsId={} catId={} skuCount={} noSpecBook={}",
                        req.getShopId(), req.getOutGoodsId(), req.getPddGoodsId(), catId, skuRows.size(), noSpecBookPublish);
                String updateJson = paramBuilder.toInformationUpdateParamJson(goodsAddRootJson, req.getPddGoodsId());
                raw = pddPopClient.invokeGoodsInformationUpdate(gateway, cred.getAppKey(), cred.getAppSecret(),
                        cred.getAccessToken(), updateJson);
                lane = "INFORMATION_UPDATE";
                okMsg = "pdd.goods.information.update 成功";
                failPrefix = "pdd.goods.information.update 失败";
            } else {
                log.info("[PDD] pdd.goods.add begin shopId={} outGoodsId={} catId={} skuCount={} noSpecBook={}",
                        req.getShopId(), req.getOutGoodsId(), catId, skuRows.size(), noSpecBookPublish);
                raw = pddPopClient.invokeGoodsAdd(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
                        goodsAddRootJson);
                lane = "GOODS_ADD";
                okMsg = "pdd.goods.add 调用成功";
                failPrefix = "pdd.goods.add 失败";
            }
            boolean ok;
            String errMsg;
            if (useInfoUpdate) {
                ok = PddOpenApiSupport.isInformationUpdateBizOk(raw);
                errMsg = ok ? null
                        : (PddOpenApiSupport.isError(raw)
                        ? PddOpenApiSupport.formatError(raw)
                        : PddOpenApiSupport.summarizeInformationUpdateFailure(raw));
            } else {
                ok = !PddOpenApiSupport.isError(raw);
                errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
            }
            Long pddGoodsId = ok
                    ? (useInfoUpdate ? req.getPddGoodsId() : PddOpenApiSupport.parseGoodsIdFromGoodsAddResponse(raw))
                    : null;
            if (ok) {
                log.info("[PDD] {} end shopId={} outGoodsId={} success=true pddGoodsId={} snippet={}",
                        useInfoUpdate ? "information.update" : "pdd.goods.add",
                        req.getShopId(), req.getOutGoodsId(), pddGoodsId,
                        PddOpenApiSupport.snippet(raw, 800));
            } else {
                log.warn("[PDD] {} end shopId={} outGoodsId={} success=false pddGoodsId={} err={} snippet={}",
                        useInfoUpdate ? "information.update" : "pdd.goods.add",
                        req.getShopId(), req.getOutGoodsId(),
                        useInfoUpdate ? req.getPddGoodsId() : null,
                        errMsg, PddOpenApiSupport.snippet(raw, 800));
            }
            return PddPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(ok)
                    .message(ok ? okMsg : (failPrefix + ": " + errMsg))
                    .goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000))
                    .pddGoodsId(pddGoodsId)
                    .shopCredentialSource(cred.getSource())
                    .catRuleFetched(catFetched)
                    .catRuleSnippet(catRuleSnippet)
                    .specAutoResolved(specAuto)
                    .autoResolveDetail(autoDetail)
                    .publishLane(lane)
                    .build();
        } catch (Exception e) {
            log.warn("拼多多发布异常 shopId={} outGoodsId={} err={}", req.getShopId(), req.getOutGoodsId(), e.getMessage(), e);
            return PddPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(false)
                    .message(e.getMessage())
                    .shopCredentialSource(cred.getSource())
                    .catRuleFetched(catFetched)
                    .catRuleSnippet(catRuleSnippet)
                    .specAutoResolved(specAuto)
                    .autoResolveDetail(autoDetail)
                    .publishLane("GOODS_ADD")
                    .build();
        }
    }

    /**
     * 下架：{@code pdd.goods.sale.status.set}，{@code is_onsale=0}（与开放平台表单顶层参数一致）。
     */
    public PddPublishLaneResultVo pddSaleStatusSetForDelist(ExternalGoodsDelistRequest req) {
        if (req == null || req.getPddGoodsId() == null || req.getPddGoodsId() <= 0) {
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(null)
                    .message("未传 pddGoodsId，跳过拼多多下架在售状态")
                    .build();
        }
        if (!props.isSaleStatusSetEnabled()) {
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(null)
                    .message("external.pdd.sale-status-set-enabled=false，未调用 pdd.goods.sale.status.set")
                    .build();
        }
        PddShopCredential cred = credentialFromUpsertAuth(req.getPddPopAuth());
        if (cred == null || !StringUtils.hasText(cred.getAppKey()) || !StringUtils.hasText(cred.getAppSecret())
                || !StringUtils.hasText(cred.getAccessToken())) {
            return PddPublishLaneResultVo.builder()
                    .attempted(false)
                    .success(false)
                    .message("下架同步拼多多需提供 pddPopAuth（appKey、appSecret、accessToken）")
                    .shopCredentialSource("NONE")
                    .build();
        }
        String gateway = StringUtils.hasText(cred.getGatewayUrl()) ? cred.getGatewayUrl() : props.getGatewayUrl();
        return invokePddGoodsSaleStatusSet(req.getShopId(), req.getOutGoodsId(), cred, gateway, req.getPddGoodsId(), 0);
    }

    private PddPublishLaneResultVo invokePddGoodsSaleStatusSet(Long shopId, String outGoodsId, PddShopCredential cred,
                                                               String gateway, long goodsId, int isOnsale) {
        try {
            log.info("[PDD] pdd.goods.sale.status.set begin shopId={} outGoodsId={} goodsId={} is_onsale={}",
                    shopId, outGoodsId, goodsId, isOnsale);
            Map<String, String> biz = new LinkedHashMap<>(2);
            biz.put("goods_id", String.valueOf(goodsId));
            biz.put("is_onsale", String.valueOf(isOnsale));
            String raw = pddPopClient.invokeTopLevelBiz(gateway, cred.getAppKey(), cred.getAppSecret(), cred.getAccessToken(),
                    "pdd.goods.sale.status.set", biz);
            boolean ok = !PddOpenApiSupport.isError(raw);
            String errMsg = ok ? null : PddOpenApiSupport.formatError(raw);
            if (ok) {
                log.info("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} is_onsale={} snippet={}",
                        shopId, outGoodsId, goodsId, isOnsale, PddOpenApiSupport.snippet(raw, 400));
            } else {
                log.warn("[PDD] pdd.goods.sale.status.set end shopId={} outGoodsId={} goodsId={} fail err={} snippet={}",
                        shopId, outGoodsId, goodsId, errMsg, PddOpenApiSupport.snippet(raw, 800));
            }
            return PddPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(ok)
                    .message(ok ? "pdd.goods.sale.status.set 成功" : ("pdd.goods.sale.status.set 失败: " + errMsg))
                    .goodsAddSnippet(PddOpenApiSupport.snippet(raw, 2000))
                    .pddGoodsId(goodsId)
                    .shopCredentialSource(cred.getSource())
                    .catRuleFetched(false)
                    .specAutoResolved(false)
                    .publishLane("SALE_STATUS_SET")
                    .build();
        } catch (Exception e) {
            log.warn("pdd.goods.sale.status.set 异常 shopId={} outGoodsId={} goodsId={} err={}",
                    shopId, outGoodsId, goodsId, e.getMessage(), e);
            return PddPublishLaneResultVo.builder()
                    .attempted(true)
                    .success(false)
                    .message("pdd.goods.sale.status.set 异常: " + e.getMessage())
                    .shopCredentialSource(cred.getSource())
                    .publishLane("SALE_STATUS_SET")
                    .build();
        }
    }

    private PddShopCredential resolveCredentialFromRequest(ExternalGoodsUpsertRequest req) {
        if (req == null || req.getPddPopAuth() == null) {
            return null;
        }
        return credentialFromUpsertAuth(req.getPddPopAuth());
    }

    private PddShopCredential credentialFromUpsertAuth(ExternalGoodsUpsertRequest.PddPopAuth a) {
        if (a == null) {
            return null;
        }
        PddShopCredential c = new PddShopCredential();
        c.setAppKey(trimToNull(a.getAppKey()));
        c.setAppSecret(trimToNull(a.getAppSecret()));
        c.setAccessToken(trimToNull(a.getAccessToken()));
        if (StringUtils.hasText(a.getGatewayUrl())) {
            c.setGatewayUrl(a.getGatewayUrl().trim());
        } else {
            c.setGatewayUrl(props.getGatewayUrl());
        }
        c.setSource("REQUEST");
        return c;
    }

    private static String trimToNull(String s) {
        if (!StringUtils.hasText(s)) {
            return null;
        }
        return s.trim();
    }

    /**
     * 供 {@code goods_properties} 自动补全：优先规格解析链路上的 cat.rule，其次 {@code auto-fetch-cat-rule} 拉取结果，最后再请求一次。
     */
    private String resolveCatRuleRawForGoodsProperties(PddShopCredential cred, String gateway, long catId,
                                                       PddCatRuleSpecAutoResolver.AutoResolveResult resolveResult,
                                                       String autoFetchedCatRuleRaw,
                                                       ExternalPddProperties props,
                                                       ExternalGoodsUpsertRequest req) {
        if (!props.isFillGoodsPropertiesFromCatRule()) {
            return null;
        }
        String raw = resolveResult != null ? resolveResult.getCatRuleRaw() : null;
        if (usableCatRuleJson(raw)) {
            return raw;
        }
        if (usableCatRuleJson(autoFetchedCatRuleRaw)) {
            return autoFetchedCatRuleRaw;
        }
        try {
            return catRuleSpecAutoResolver.fetchCatRuleJson(cred, gateway, catId);
        } catch (Exception e) {
            log.warn("pdd.goods.cat.rule.get (goods_properties) shopId={} outGoodsId={} catId={} err={}",
                    req != null ? req.getShopId() : null,
                    req != null ? req.getOutGoodsId() : null,
                    catId,
                    e.getMessage());
            return null;
        }
    }

    private static boolean usableCatRuleJson(String raw) {
        return StringUtils.hasText(raw)
                && !PddOpenApiSupport.isError(raw)
                && raw.contains("goods_properties_rule");
    }
}
