package cn.qihangerp.service.external.pdd;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * 拼多多发品前「商品编辑」线图片素材校验（轮播 / 商详 / SKU）。
 * <p>
 * 规则来源：运营/平台对「商品编辑」素材的要求整理；其中<strong>商详图宽高比</strong>采用业务确认的
 * <strong>理解 A</strong>：{@code 宽/高 > 1/3}（见 {@link PddGoodsMaterialRole#DETAIL} 注释）。
 * </p>
 * <p>
 * 校验在<strong>已下载的图片字节</strong>上进行：文件大小取字节数组长度；像素尺寸依赖 {@link ImageIO}，
 * 若环境无法解码（如部分 WEBP），将抛出带说明的异常，建议源站改为 JPG/PNG。
 * </p>
 */
public final class PddGoodsMaterialImageValidator {

    /*
     * -------------------------------------------------------------------------
     * 商品编辑 — 轮播图（carousel_gallery）
     * -------------------------------------------------------------------------
     * - 等宽高：展示区域为正方形，故要求解码后 width == height（整数像素相等）。
     * - 最小边长：文档「最小 480px」按闭区间处理，即 width >= 480 且 height >= 480；与等宽结合即边长 >= 480。
     * - 文件大小：单张不超过 1MB（1024*1024 字节）。
     */
    private static final long CAROUSEL_MAX_BYTES = 1024L * 1024L;
    private static final int CAROUSEL_MIN_SIDE = 480;

    /*
     * -------------------------------------------------------------------------
     * 商品编辑 — 商详图（detail_gallery）
     * -------------------------------------------------------------------------
     * - 宽度：文档要求宽度最小 480px，即 width >= 480。
     * - 宽高比（理解 A）：要求 宽/高 > 1/3（严格大于）。
     *   含义：避免竖图过高；例如宽 400、高 1200 时 400/1200 = 1/3，不满足「>」，不通过；
     *   高略减至 1199 则 400/1199 > 1/3，通过（在整数像素下）。
     *   分母 height 须 > 0，否则视为无法校验。
     * - 文件大小：单张不超过 1MB。
     */
    private static final long DETAIL_MAX_BYTES = 1024L * 1024L;
    private static final int DETAIL_MIN_WIDTH = 480;
    /** 商详图：宽/高 须严格大于 1/3 */
    private static final double DETAIL_MIN_WIDTH_OVER_HEIGHT = 1.0 / 3.0;

    /*
     * -------------------------------------------------------------------------
     * 商品编辑 — SKU 图（sku_list[].thumb_url）
     * -------------------------------------------------------------------------
     * - 比例 1:1：width == height。
     * - 边长：文档「宽和高均大于 480px」按严格大于处理，即 width > 480 且 height > 480
     *   （整数像素下等价于至少 481×481）。
     * - 文件大小：单张不超过 1MB。
     */
    private static final long SKU_MAX_BYTES = 1024L * 1024L;
    private static final int SKU_MIN_SIDE_EXCLUSIVE = 480;

    private PddGoodsMaterialImageValidator() {
    }

    /**
     * 对同一批字节按所承担的全部 {@link PddGoodsMaterialRole} 依次校验，全部通过则返回；
     * 否则抛出 {@link IllegalStateException}，信息中拼接所有违规说明。
     *
     * @param imageBytes 已下载的完整图片文件内容（用于文件大小与解码）
     * @param roles      该 URL 在商品中出现的角色集合（非空）
     * @param urlSnippet 用于错误信息中的 URL 摘要（已脱敏/截断由调用方决定）
     */
    public static void validate(byte[] imageBytes, Set<PddGoodsMaterialRole> roles, String urlSnippet) {
        if (roles == null || roles.isEmpty()) {
            return;
        }
        if (imageBytes == null || imageBytes.length == 0) {
            throw new IllegalStateException("素材校验失败：内容为空 url=" + urlSnippet);
        }
        BufferedImage img;
        try {
            img = ImageIO.read(new ByteArrayInputStream(imageBytes));
        } catch (IOException e) {
            throw new IllegalStateException("素材校验失败：无法解码图片（请使用 JPG/PNG 等常见格式） url=" + urlSnippet + " err=" + e.getMessage());
        }
        if (img == null) {
            throw new IllegalStateException("素材校验失败：ImageIO 无法识别图片格式 url=" + urlSnippet);
        }
        int w = img.getWidth();
        int h = img.getHeight();
        if (w <= 0 || h <= 0) {
            throw new IllegalStateException("素材校验失败：图片尺寸无效 width=" + w + " height=" + h + " url=" + urlSnippet);
        }

        List<String> violations = new ArrayList<>();
        for (PddGoodsMaterialRole role : roles) {
            switch (role) {
                case CAROUSEL -> validateCarousel(imageBytes.length, w, h, violations);
                case DETAIL -> validateDetail(imageBytes.length, w, h, violations);
                case SKU_THUMB -> validateSku(imageBytes.length, w, h, violations);
            }
        }
        if (!violations.isEmpty()) {
            throw new IllegalStateException("素材不符合拼多多商品编辑规则 url=" + urlSnippet + " — " + String.join("；", violations));
        }
    }

    private static void validateCarousel(long fileBytes, int w, int h, List<String> out) {
        String tag = "[轮播图]";
        if (fileBytes > CAROUSEL_MAX_BYTES) {
            out.add(tag + "文件须≤1MB，当前约 " + formatBytes(fileBytes));
        }
        if (w != h) {
            out.add(tag + "须等宽高（1:1），当前 " + w + "×" + h);
        }
        if (w < CAROUSEL_MIN_SIDE || h < CAROUSEL_MIN_SIDE) {
            out.add(tag + "边长须≥480px，当前 " + w + "×" + h);
        }
    }

    private static void validateDetail(long fileBytes, int w, int h, List<String> out) {
        String tag = "[商详图]";
        if (fileBytes > DETAIL_MAX_BYTES) {
            out.add(tag + "文件须≤1MB，当前约 " + formatBytes(fileBytes));
        }
        if (w < DETAIL_MIN_WIDTH) {
            out.add(tag + "宽度须≥480px，当前 width=" + w);
        }
        if (h <= 0) {
            out.add(tag + "高度无效");
            return;
        }
        double ratio = w / (double) h;
        if (!(ratio > DETAIL_MIN_WIDTH_OVER_HEIGHT)) {
            out.add(tag + "须满足 宽÷高>1/3（避免过竖长图；等价 高<3×宽），当前 " + w + "×" + h
                    + "（宽÷高=" + String.format(Locale.ROOT, "%.4f", ratio) + "）");
        }
    }

    private static void validateSku(long fileBytes, int w, int h, List<String> out) {
        String tag = "[SKU图]";
        if (fileBytes > SKU_MAX_BYTES) {
            out.add(tag + "文件须≤1MB，当前约 " + formatBytes(fileBytes));
        }
        if (w != h) {
            out.add(tag + "须 1:1（等宽高），当前 " + w + "×" + h);
        }
        if (w <= SKU_MIN_SIDE_EXCLUSIVE || h <= SKU_MIN_SIDE_EXCLUSIVE) {
            out.add(tag + "宽、高均须>480px（严格大于），当前 " + w + "×" + h);
        }
    }

    private static String formatBytes(long b) {
        if (b < 1024) {
            return b + "B";
        }
        if (b < 1024 * 1024) {
            return String.format(Locale.ROOT, "%.1fKB", b / 1024.0);
        }
        return String.format(Locale.ROOT, "%.2fMB", b / (1024.0 * 1024.0));
    }
}
