package cn.qihangerp.service.external.pdd;

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.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 发品前将 {@code pdd.goods.add} 根 JSON 中的外链图片经 {@code pdd.goods.image.upload}（urlencoded + Base64 {@code image}）上传并替换 URL。
 * <p>若 {@link ExternalPddProperties#isMaterialValidationEnabled()} 为 true，在每张图下载后、上传前按
 * {@link PddGoodsMaterialImageValidator} 做「商品编辑」线规则校验（轮播/商详/SKU，规则见各枚举与校验类注释）。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PddGoodsImageRehostService {

    private final ExternalPddProperties props;
    private final PddPopClient pddPopClient;

    private final HttpClient downloadClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(15))
            .followRedirects(HttpClient.Redirect.NORMAL)
            .build();

    /**
     * 若 {@link ExternalPddProperties#isImageUploadEnabled()} 为 false，原样返回。
     */
    public String rehostExternalImagesInGoodsAddJson(String goodsAddRootJson, String gatewayUrl,
                                                     String clientId, String clientSecret, String accessToken) throws Exception {
        if (!props.isImageUploadEnabled()) {
            return goodsAddRootJson;
        }
        if (!StringUtils.hasText(goodsAddRootJson)) {
            return goodsAddRootJson;
        }
        JSONObject root = JSON.parseObject(goodsAddRootJson);
        if (root == null || root.isEmpty()) {
            return goodsAddRootJson;
        }
        Map<String, Set<PddGoodsMaterialRole>> urlToRoles = collectUrlsWithRolesNeedingRehost(root);
        if (urlToRoles.isEmpty()) {
            log.debug("[PDD] image rehost skipped (no URLs requiring upload)");
            return goodsAddRootJson;
        }
        log.info("[PDD] image rehost begin distinctUrls={} gatewayHost={}", urlToRoles.size(), hostForLog(gatewayUrl));
        Map<String, String> sourceToPdd = new LinkedHashMap<>();
        for (Map.Entry<String, Set<PddGoodsMaterialRole>> e : urlToRoles.entrySet()) {
            String src = e.getKey();
            Set<PddGoodsMaterialRole> roles = e.getValue();
            if (sourceToPdd.containsKey(src)) {
                continue;
            }
            byte[] bytes = downloadImageBytes(src);
            if (props.isMaterialValidationEnabled()) {
                PddGoodsMaterialImageValidator.validate(bytes, roles, PddOpenApiSupport.snippet(src, 96));
            }
            String raw = pddPopClient.invokeGoodsImageUpload(gatewayUrl, clientId, clientSecret, accessToken, bytes);
            String pddUrl = PddOpenApiSupport.parseGoodsImgUploadUrl(raw);
            if (!StringUtils.hasText(pddUrl)) {
                throw new IllegalStateException("pdd.goods.image.upload 失败: " + PddOpenApiSupport.formatError(raw));
            }
            sourceToPdd.put(src, pddUrl);
            log.info("[PDD] image rehost ok srcSnippet={} bytes={} destSnippet={}",
                    PddOpenApiSupport.snippet(src, 96), bytes.length, PddOpenApiSupport.snippet(pddUrl, 96));
        }
        applyImageMapping(root, sourceToPdd);
        return root.toJSONString();
    }

    /**
     * 收集需上传的外链 URL，并为每个 URL 标注其在商品中出现的素材角色（轮播/商详/SKU），供校验时套用对应规则。
     */
    private Map<String, Set<PddGoodsMaterialRole>> collectUrlsWithRolesNeedingRehost(JSONObject root) {
        Map<String, Set<PddGoodsMaterialRole>> map = new LinkedHashMap<>();
        addUrlsWithRole(root.getJSONArray("carousel_gallery"), map, PddGoodsMaterialRole.CAROUSEL);
        addUrlsWithRole(root.getJSONArray("detail_gallery"), map, PddGoodsMaterialRole.DETAIL);
        JSONArray skuList = root.getJSONArray("sku_list");
        if (skuList != null) {
            for (int i = 0; i < skuList.size(); i++) {
                JSONObject sku = skuList.getJSONObject(i);
                if (sku != null) {
                    String u = sku.getString("thumb_url");
                    if (StringUtils.hasText(u)) {
                        addRole(map, u.trim(), PddGoodsMaterialRole.SKU_THUMB);
                    }
                }
            }
        }
        Map<String, Set<PddGoodsMaterialRole>> out = new LinkedHashMap<>();
        for (Map.Entry<String, Set<PddGoodsMaterialRole>> e : map.entrySet()) {
            if (needsRehost(e.getKey())) {
                out.put(e.getKey(), e.getValue());
            }
        }
        return out;
    }

    private static void addUrlsWithRole(JSONArray arr, Map<String, Set<PddGoodsMaterialRole>> map, PddGoodsMaterialRole role) {
        if (arr == null || arr.isEmpty()) {
            return;
        }
        for (int i = 0; i < arr.size(); i++) {
            Object el = arr.get(i);
            if (el == null) {
                continue;
            }
            String u = el instanceof String s ? s : el.toString();
            if (StringUtils.hasText(u)) {
                addRole(map, u.trim(), role);
            }
        }
    }

    private static void addRole(Map<String, Set<PddGoodsMaterialRole>> map, String url, PddGoodsMaterialRole role) {
        map.computeIfAbsent(url, k -> EnumSet.noneOf(PddGoodsMaterialRole.class)).add(role);
    }

    private boolean needsRehost(String url) {
        if (!StringUtils.hasText(url)) {
            return false;
        }
        String u = url.trim();
        if (!u.startsWith("http://") && !u.startsWith("https://")) {
            return false;
        }
        String host = hostOf(u);
        if (!StringUtils.hasText(host)) {
            return false;
        }
        if (hostMatchesAnySuffix(host, props.getImageUploadSkipHostSuffixes())) {
            return false;
        }
        if (!hostMatchesAllowedSuffix(host)) {
            throw new IllegalStateException("图片 URL 域名不在 external.pdd.image-upload-allowed-host-suffixes 白名单: "
                    + host + " url=" + PddOpenApiSupport.snippet(u, 96));
        }
        return true;
    }

    private boolean hostMatchesAllowedSuffix(String host) {
        String h = host.toLowerCase(Locale.ROOT);
        List<String> suffixes = props.getImageUploadAllowedHostSuffixes();
        if (suffixes == null || suffixes.isEmpty()) {
            return false;
        }
        return hostMatchesAnySuffix(h, suffixes);
    }

    private static boolean hostMatchesAnySuffix(String hostLower, List<String> suffixes) {
        if (suffixes == null) {
            return false;
        }
        for (String suf : suffixes) {
            if (suf == null || suf.isBlank()) {
                continue;
            }
            String s = suf.trim().toLowerCase(Locale.ROOT);
            if (hostLower.equals(s) || hostLower.endsWith("." + s)) {
                return true;
            }
        }
        return false;
    }

    private static String hostOf(String url) {
        try {
            URI uri = URI.create(url.trim());
            String h = uri.getHost();
            return h != null ? h.toLowerCase(Locale.ROOT) : null;
        } catch (Exception e) {
            return null;
        }
    }

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

    private byte[] downloadImageBytes(String urlString) throws Exception {
        long max = props.getImageUploadMaxBytes();
        URI uri = URI.create(urlString.trim());
        HttpRequest req = HttpRequest.newBuilder(uri)
                .timeout(Duration.ofSeconds(90))
                .header("User-Agent", "qihang-erp-open/pdd-image-rehost")
                .GET()
                .build();
        HttpResponse<InputStream> resp = downloadClient.send(req, HttpResponse.BodyHandlers.ofInputStream());
        int code = resp.statusCode();
        try (InputStream in = resp.body()) {
            if (code < 200 || code >= 300) {
                drainQuietly(in);
                throw new IllegalStateException("下载图片 HTTP " + code + ": " + PddOpenApiSupport.snippet(urlString, 100));
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream(
                    (int) Math.min(max, Math.min(Integer.MAX_VALUE, 256L * 1024)));
            byte[] buf = new byte[8192];
            long total = 0;
            int n;
            while ((n = in.read(buf)) >= 0) {
                total += n;
                if (total > max) {
                    throw new IllegalStateException("图片超过 image-upload-max-bytes=" + max + "，url="
                            + PddOpenApiSupport.snippet(urlString, 96));
                }
                baos.write(buf, 0, n);
            }
            byte[] body = baos.toByteArray();
            if (body.length == 0) {
                throw new IllegalStateException("下载图片内容为空: " + PddOpenApiSupport.snippet(urlString, 100));
            }
            if (!looksLikeImageMagic(body)) {
                log.warn("[PDD] image download magic check inconclusive url={}", PddOpenApiSupport.snippet(urlString, 96));
            }
            return body;
        }
    }

    private static void drainQuietly(InputStream in) {
        try {
            byte[] buf = new byte[8192];
            while (in.read(buf) >= 0) {
                // discard
            }
        } catch (Exception ignored) {
        }
    }

    private static boolean looksLikeImageMagic(byte[] b) {
        if (b == null || b.length < 12) {
            return false;
        }
        if (b[0] == (byte) 0xFF && b[1] == (byte) 0xD8 && b[2] == (byte) 0xFF) {
            return true;
        }
        if (b[0] == (byte) 0x89 && b[1] == 'P' && b[2] == 'N' && b[3] == 'G') {
            return true;
        }
        if (b[0] == 'G' && b[1] == 'I' && b[2] == 'F') {
            return true;
        }
        return b.length >= 12 && b[0] == 'R' && b[1] == 'I' && b[2] == 'F' && b[3] == 'F'
                && b[8] == 'W' && b[9] == 'E' && b[10] == 'B' && b[11] == 'P';
    }

    private void applyImageMapping(JSONObject root, Map<String, String> mapping) {
        if (mapping.isEmpty()) {
            return;
        }
        replaceInJsonArray(root.getJSONArray("carousel_gallery"), mapping);
        replaceInJsonArray(root.getJSONArray("detail_gallery"), mapping);
        JSONArray skuList = root.getJSONArray("sku_list");
        if (skuList != null) {
            for (int i = 0; i < skuList.size(); i++) {
                JSONObject sku = skuList.getJSONObject(i);
                if (sku == null) {
                    continue;
                }
                String t = sku.getString("thumb_url");
                if (StringUtils.hasText(t)) {
                    String repl = mapping.get(t.trim());
                    if (repl != null) {
                        sku.put("thumb_url", repl);
                    }
                }
            }
        }
    }

    private static void replaceInJsonArray(JSONArray arr, Map<String, String> mapping) {
        if (arr == null || arr.isEmpty()) {
            return;
        }
        for (int i = 0; i < arr.size(); i++) {
            Object el = arr.get(i);
            if (el == null) {
                continue;
            }
            String u = el instanceof String s ? s : el.toString();
            if (!StringUtils.hasText(u)) {
                continue;
            }
            String repl = mapping.get(u.trim());
            if (repl != null) {
                arr.set(i, repl);
            }
        }
    }
}
