package cn.qihangerp.service.external.pdd;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 拼多多 POP HTTP 调用（application/x-www-form-urlencoded）。
 * <p>每次请求无论成功失败均打日志（含 HTTP 状态、耗时、响应片段；client_id 脱敏）。</p>
 * <p><b>入参形态（与官方 POP curl / Java SDK 一致）：</b></p>
 * <ul>
 *   <li>{@link #invokeGoodsAdd} / {@link #invokeGoodsInformationUpdate} — {@code pdd.goods.add}、{@code pdd.goods.information.update}：
 *       根 JSON 展开为表单顶层（与官方 curl / Java SDK 一致），<b>无</b> {@code param_json}、<b>无</b> {@code version}</li>
 *   <li>{@link #invokeTopLevelBiz} — 如 {@code pdd.goods.cat.rule.get}、{@code pdd.goods.quantity.update} 的顶层字段</li>
 *   <li>{@link #invokeGoodsImageUpload} — {@code pdd.goods.image.upload}：urlencoded，{@code image} 为 {@code data:image/*;base64,} + Base64</li>
 *   <li>{@link #invoke} — 仅当接口要求整包 {@code param_json} 时使用（少数场景）</li>
 * </ul>
 */
@Slf4j
@Component
public class PddPopClient {

    private final HttpClient httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(15))
            .build();

    /**
     * {@code pdd.goods.add}：将 {@link cn.qihangerp.service.external.pdd.PddGoodsAddParamBuilder} 生成的根 JSON 展开为顶层表单后调用网关。
     */
    public String invokeGoodsAdd(String gatewayUrl, String clientId, String clientSecret, String accessToken,
                                 String goodsAddRootJson) throws Exception {
        Map<String, String> biz = PddOpenApiSupport.flattenPopTopLevelFromRootJson(goodsAddRootJson);
        String logPayload = PddOpenApiSupport.snippet(goodsAddRootJson, 400);
        if (!StringUtils.hasText(gatewayUrl)) {
            throw new IllegalArgumentException("gatewayUrl 不能为空");
        }
        Map<String, String> params = buildBaseParams(clientId, accessToken, "pdd.goods.add", false);
        if (biz != null) {
            for (Map.Entry<String, String> e : biz.entrySet()) {
                if (e.getKey() == null || e.getValue() == null) {
                    continue;
                }
                params.put(e.getKey(), e.getValue());
            }
        }
        return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.add", params, logPayload, Duration.ofSeconds(60));
    }

    /**
     * {@code pdd.goods.information.update}：与 {@link #invokeGoodsAdd} 相同，将根 JSON 展开为顶层表单（官方示例 {@code sku_list}、{@code goods_id} 等与 {@code type} 同级）。
     */
    public String invokeGoodsInformationUpdate(String gatewayUrl, String clientId, String clientSecret, String accessToken,
                                               String informationUpdateRootJson) throws Exception {
        Map<String, String> biz = PddOpenApiSupport.flattenPopTopLevelFromRootJson(informationUpdateRootJson);
        String logPayload = PddOpenApiSupport.snippet(informationUpdateRootJson, 400);
        if (!StringUtils.hasText(gatewayUrl)) {
            throw new IllegalArgumentException("gatewayUrl 不能为空");
        }
        Map<String, String> params = buildBaseParams(clientId, accessToken, "pdd.goods.information.update", false);
        if (biz != null) {
            for (Map.Entry<String, String> e : biz.entrySet()) {
                if (e.getKey() == null || e.getValue() == null) {
                    continue;
                }
                params.put(e.getKey(), e.getValue());
            }
        }
        return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.information.update", params, logPayload,
                Duration.ofSeconds(60));
    }

    public String invoke(String gatewayUrl, String clientId, String clientSecret, String accessToken,
                         String type, String paramJson) throws Exception {
        if (!StringUtils.hasText(gatewayUrl)) {
            throw new IllegalArgumentException("gatewayUrl 不能为空");
        }
        Map<String, String> params = buildBaseParams(clientId, accessToken, type, true);
        if (StringUtils.hasText(paramJson)) {
            params.put("param_json", paramJson);
        }
        String logPayload = StringUtils.hasText(paramJson) ? PddOpenApiSupport.snippet(paramJson, 400) : "";
        return postSignedAndLog(gatewayUrl, clientId, clientSecret, type, params, logPayload, Duration.ofSeconds(60));
    }

    /**
     * 业务参数全部在表单顶层（与官方 {@code curl} / Java SDK 一致），例如
     * {@code pdd.goods.cat.rule.get} 的 {@code cat_id}、{@code goods_id}。
     * <p>不包含 {@code param_json}；{@code version} 也不加入（与官方示例 curl 一致，避免签名校验差异）。</p>
     */
    public String invokeTopLevelBiz(String gatewayUrl, String clientId, String clientSecret, String accessToken,
                                    String type, Map<String, String> topLevelBizParams) throws Exception {
        if (!StringUtils.hasText(gatewayUrl)) {
            throw new IllegalArgumentException("gatewayUrl 不能为空");
        }
        Map<String, String> params = buildBaseParams(clientId, accessToken, type, false);
        if (topLevelBizParams != null) {
            for (Map.Entry<String, String> e : topLevelBizParams.entrySet()) {
                if (e.getKey() == null || e.getValue() == null) {
                    continue;
                }
                params.put(e.getKey(), e.getValue());
            }
        }
        String logPayload = topLevelBizParams == null ? "" : PddOpenApiSupport.snippet(topLevelBizParams.toString(), 400);
        return postSignedAndLog(gatewayUrl, clientId, clientSecret, type, params, logPayload, Duration.ofSeconds(60));
    }

    /**
     * {@code pdd.goods.image.upload}：{@code application/x-www-form-urlencoded}。
     * <p>参数 {@code image} 为必填 STRING：常见对接要求为 {@code data:image/jpeg;base64,} 或 {@code data:image/png;base64,}
     * 后接<strong>标准 Base64</strong>（无换行）。仅传裸 Base64 时网关可能返回「图片格式错误」。</p>
     */
    public String invokeGoodsImageUpload(String gatewayUrl, String clientId, String clientSecret, String accessToken,
                                         byte[] imageBytes) throws Exception {
        if (!StringUtils.hasText(gatewayUrl)) {
            throw new IllegalArgumentException("gatewayUrl 不能为空");
        }
        if (imageBytes == null || imageBytes.length == 0) {
            throw new IllegalArgumentException("image 不能为空");
        }
        String imageParam = buildPddGoodsImageUploadParam(imageBytes);
        Map<String, String> params = buildBaseParams(clientId, accessToken, "pdd.goods.image.upload", false);
        params.put("image", imageParam);
        String logPayload = "pdd.goods.image.upload image=data:*;base64,(totalChars=" + imageParam.length() + ") rawBytes="
                + imageBytes.length;
        return postSignedAndLog(gatewayUrl, clientId, clientSecret, "pdd.goods.image.upload", params, logPayload,
                Duration.ofSeconds(180));
    }

    /**
     * 组装 {@code pdd.goods.image.upload} 的 {@code image} 字段：{@code data:{mime};base64,}{base64}。
     */
    static String buildPddGoodsImageUploadParam(byte[] imageBytes) {
        String mime = guessImageMimeForPddUpload(imageBytes);
        String b64 = Base64.getEncoder().encodeToString(imageBytes);
        return "data:" + mime + ";base64," + b64;
    }

    /** 按文件头识别 MIME；无法识别时按 jpeg 声明（仍可能因非图片内容被平台拒绝）。 */
    private static String guessImageMimeForPddUpload(byte[] b) {
        if (b == null || b.length < 12) {
            return "image/jpeg";
        }
        if (b[0] == (byte) 0xFF && b[1] == (byte) 0xD8 && b[2] == (byte) 0xFF) {
            return "image/jpeg";
        }
        if (b[0] == (byte) 0x89 && b[1] == 'P' && b[2] == 'N' && b[3] == 'G') {
            return "image/png";
        }
        if (b[0] == 'G' && b[1] == 'I' && b[2] == 'F') {
            return "image/gif";
        }
        if (b[0] == 'R' && b[1] == 'I' && b[2] == 'F' && b[3] == 'F'
                && b[8] == 'W' && b[9] == 'E' && b[10] == 'B' && b[11] == 'P') {
            return "image/webp";
        }
        if (b[0] == 'B' && b[1] == 'M') {
            return "image/bmp";
        }
        return "image/jpeg";
    }

    private static Map<String, String> buildBaseParams(String clientId, String accessToken, String type,
                                                         boolean withVersion) {
        long ts = System.currentTimeMillis() / 1000L;
        Map<String, String> params = new HashMap<>();
        params.put("type", type);
        params.put("client_id", clientId);
        params.put("timestamp", String.valueOf(ts));
        params.put("data_type", "JSON");
        if (withVersion) {
            params.put("version", "V1");
        }
        if (StringUtils.hasText(accessToken)) {
            params.put("access_token", accessToken);
        }
        return params;
    }

    private String postSignedAndLog(String gatewayUrl, String clientId, String clientSecret, String type,
                                    Map<String, String> params, String paramLogSnippet, Duration requestTimeout) throws Exception {
        String sign = PddPopSignUtil.sign(params, clientSecret);
        params.put("sign", sign);

        String body = formEncode(params);
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(gatewayUrl))
                .timeout(requestTimeout != null ? requestTimeout : Duration.ofSeconds(60))
                .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
                .build();
        String host = safeHostForLog(gatewayUrl);
        String clientMasked = PddSensitiveLogUtil.maskForLog(clientId);
        long t0 = System.nanoTime();
        try {
            HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
            long durationMs = (System.nanoTime() - t0) / 1_000_000L;
            int httpStatus = resp.statusCode();
            String raw = resp.body();
            boolean httpOk = httpStatus >= 200 && httpStatus < 300;
            boolean popBizError = PddOpenApiSupport.isError(raw);
            if ("pdd.goods.cat.rule.get".equals(type)) {
                String catSummary = PddOpenApiSupport.summarizeCatRuleGetForLog(raw);
                String bodyLong = PddOpenApiSupport.snippet(raw, PddOpenApiSupport.CAT_RULE_GET_LOG_BODY_SNIPPET_MAX);
                if (log.isDebugEnabled()) {
                    log.debug("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} catRuleSummary={} fullBody={}",
                            type, host, clientMasked, httpStatus, durationMs, catSummary, raw);
                }
                if (!httpOk || popBizError) {
                    String errSummary = popBizError ? PddOpenApiSupport.formatError(raw) : "";
                    log.warn("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} popBizError={} errSummary={} paramPayloadSnippet={} catRuleSummary={} bodySnippet={}",
                            type, host, clientMasked, httpStatus, durationMs, popBizError, errSummary,
                            paramLogSnippet != null ? paramLogSnippet : "", catSummary, bodyLong);
                } else {
                    log.info("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} catRuleSummary={} bodySnippet={}",
                            type, host, clientMasked, httpStatus, durationMs, catSummary, bodyLong);
                }
                return raw;
            }
            if ("pdd.goods.detail.get".equals(type)) {
                if (!httpOk || popBizError) {
                    String errSummary = popBizError ? PddOpenApiSupport.formatError(raw) : "";
                    log.warn("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} popBizError={} errSummary={} paramPayloadSnippet={} fullResponseBody={}",
                            type, host, clientMasked, httpStatus, durationMs, popBizError, errSummary,
                            paramLogSnippet != null ? paramLogSnippet : "", raw);
                } else {
                    log.info("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} fullResponseBody={}",
                            type, host, clientMasked, httpStatus, durationMs, raw);
                }
                return raw;
            }
            if ("pdd.goods.quantity.update".equals(type)) {
                String snippet = PddOpenApiSupport.snippet(raw, 800);
                if (!httpOk || popBizError) {
                    String errSummary = popBizError ? PddOpenApiSupport.formatError(raw) : "";
                    log.warn("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} popBizError={} errSummary={} paramPayloadSnippet={} bodySnippet={}",
                            type, host, clientMasked, httpStatus, durationMs, popBizError, errSummary,
                            paramLogSnippet != null ? paramLogSnippet : "", snippet);
                } else {
                    log.info("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} quantityOk={} bodySnippet={}",
                            type, host, clientMasked, httpStatus, durationMs,
                            PddOpenApiSupport.parseGoodsQuantityUpdateIsSuccess(raw), snippet);
                }
                return raw;
            }
            String snippet = PddOpenApiSupport.snippet(raw, 600);
            if (!httpOk || popBizError) {
                String errSummary = popBizError ? PddOpenApiSupport.formatError(raw) : "";
                log.warn("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} popBizError={} errSummary={} paramPayloadSnippet={} bodySnippet={}",
                        type, host, clientMasked, httpStatus, durationMs, popBizError, errSummary,
                        paramLogSnippet != null ? paramLogSnippet : "", snippet);
            } else {
                log.info("PDD_POP api={} host={} clientId={} httpStatus={} durationMs={} bodySnippet={}",
                        type, host, clientMasked, httpStatus, durationMs, snippet);
            }
            return raw;
        } catch (Exception e) {
            long durationMs = (System.nanoTime() - t0) / 1_000_000L;
            log.warn("PDD_POP api={} host={} clientId={} durationMs={} invokeFailed={}",
                    type, host, clientMasked, durationMs, e.toString(), e);
            throw e;
        }
    }

    private static String safeHostForLog(String gatewayUrl) {
        if (!StringUtils.hasText(gatewayUrl)) {
            return "";
        }
        try {
            URI u = URI.create(gatewayUrl.trim());
            String h = u.getHost();
            return h != null ? h : gatewayUrl.trim();
        } catch (Exception e) {
            return gatewayUrl.trim();
        }
    }

    private static String formEncode(Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> e : params.entrySet()) {
            if (sb.length() > 0) {
                sb.append('&');
            }
            sb.append(encode(e.getKey()))
                    .append('=')
                    .append(encode(e.getValue()));
        }
        return sb.toString();
    }

    private static String encode(String s) {
        return URLEncoder.encode(s == null ? "" : s, StandardCharsets.UTF_8);
    }
}
