package com.hotwj.platform.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.fontbox.ttf.TrueTypeCollection;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.util.Matrix;
import org.springframework.core.io.ClassPathResource;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * PDF Watermark Utility
 * Adds text watermarks to PDF files using PDFBox.
 *
 * @author shihongwei
 * @date 2026-01-02
 */
@Slf4j
public class PdfWatermarkUtil {

    private static final String DEFAULT_APP_NAME = "HOT交通安全管理清单平台";
    private static final String DEFAULT_WATERMARK_TEXT = "HOT交通安全\n管理清单平台";
    private static final float WATERMARK_ALPHA = 0.15f;
    private static final float PAGE_NUMBER_ALPHA = 1.0f;
    private static final int WATERMARK_FONT_SIZE = 18;
    private static final float PAGE_NUMBER_FONT_SIZE = 7.5f;
    private static final int WATERMARK_ANGLE = -30;
    private static final int WATERMARK_GAP = 200;

    private static volatile byte[] cachedFontBytes;

    /**
     * Lazy loads the font bytes from classpath
     */
    private static byte[] getFontBytes() {
        if (cachedFontBytes != null) {
            return cachedFontBytes;
        }
        synchronized (PdfWatermarkUtil.class) {
            if (cachedFontBytes == null) {
                try {
                    ClassPathResource fontResource = new ClassPathResource("fonts/simsun.ttc");
                    if (fontResource.exists()) {
                        try (InputStream is = fontResource.getInputStream()) {
                            cachedFontBytes = is.readAllBytes();
                        }
                    } else {
                        log.warn("Watermark font file not found at fonts/simsun.ttc");
                    }
                } catch (IOException e) {
                    log.error("Failed to load watermark font", e);
                }
            }
        }
        return cachedFontBytes;
    }

    /**
     * Adds a text watermark to the provided PDF bytes (optimized for Landscape A4).
     *
     * @param pdfBytes The original PDF bytes
     * @param appName  The text to use as watermark
     * @return The PDF bytes with watermark added
     */
    public static byte[] addWatermarkLandscape(byte[] pdfBytes, String appName) {
        return addWatermark(pdfBytes, appName);
    }

    /**
     * Adds a text watermark to the provided PDF bytes.
     *
     * @param pdfBytes The original PDF bytes
     * @param appName  The text to use as watermark (usually application name)
     * @return The PDF bytes with watermark added
     */
    public static byte[] addWatermark(byte[] pdfBytes, String appName) {
        byte[] fontBytes = getFontBytes();
        if (fontBytes == null) {
            log.warn("Watermark font not loaded, skipping watermark.");
            return pdfBytes;
        }

        String wmText = formatWatermarkText(appName);

        try (PDDocument doc = PDDocument.load(pdfBytes)) {
            PDFont font = loadFont(doc, fontBytes);
            if (font == null) {
                return pdfBytes;
            }

            PDExtendedGraphicsState r0 = new PDExtendedGraphicsState();
            r0.setNonStrokingAlphaConstant(WATERMARK_ALPHA);

            String[] lines = wmText.split("\n|\\\\n");

            for (PDPage page : doc.getPages()) {
                try (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    cs.setGraphicsStateParameters(r0);
                    cs.setFont(font, WATERMARK_FONT_SIZE);
                    cs.setNonStrokingColor(Color.BLACK);

                    float pageWidth = page.getMediaBox().getWidth();
                    float pageHeight = page.getMediaBox().getHeight();

                    for (int x = 0; x < pageWidth; x += WATERMARK_GAP) {
                        for (int y = 0; y < pageHeight; y += WATERMARK_GAP) {
                            drawWatermarkText(cs, lines, x, y);
                        }
                    }
                }
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Failed to add watermark to PDF", e);
        }
    }

    private static PDFont loadFont(PDDocument doc, byte[] fontBytes) {
        try (InputStream is = new ByteArrayInputStream(fontBytes)) {
            TrueTypeCollection ttc = new TrueTypeCollection(is);
            return PDType0Font.load(doc, ttc.getFontByName("SimSun"), true);
        } catch (Exception e) {
            log.error("Error loading font from cached bytes", e);
            return null;
        }
    }

    private static void drawWatermarkText(PDPageContentStream cs, String[] lines, float x, float y) throws IOException {
        cs.beginText();
        // Add offset to center within the grid cell roughly
        // Shift y up by 100 (instead of 50) to prevent bottom row cutoff
        Matrix matrix = Matrix.getRotateInstance(Math.toRadians(WATERMARK_ANGLE), x + 50, y + 100);
        cs.setTextMatrix(matrix);

        float leading = 20f;
        for (String line : lines) {
            cs.showText(line);
            cs.newLineAtOffset(0, -leading);
        }
        cs.endText();
    }

    private static String formatWatermarkText(String appName) {
        String wmText = appName;
        if (wmText == null || wmText.isEmpty() || DEFAULT_APP_NAME.equals(wmText)) {
            return DEFAULT_WATERMARK_TEXT;
        }
        if (wmText.length() > 10 && !wmText.contains("<br>") && !wmText.contains("\n")) {
            int mid = wmText.length() / 2;
            return wmText.substring(0, mid) + "\n" + wmText.substring(mid);
        }
        return wmText;
    }

    public static byte[] addSealBottomRight(byte[] pdfBytes, byte[] sealImageBytes) {
        if (pdfBytes == null || sealImageBytes == null || sealImageBytes.length == 0) {
            return pdfBytes;
        }
        try (PDDocument doc = PDDocument.load(pdfBytes)) {
            PDImageXObject image = PDImageXObject.createFromByteArray(doc, sealImageBytes, "seal");
            PDFRenderer renderer = new PDFRenderer(doc);
            float imageWidth = image.getWidth();
            float imageHeight = image.getHeight();
            int pageIndex = 0;
            for (PDPage page : doc.getPages()) {
                float pageWidth = page.getMediaBox().getWidth();
                float pageHeight = page.getMediaBox().getHeight();
                float targetWidth = pageWidth * 0.18f;
                float scale = targetWidth / imageWidth;
                float targetHeight = imageHeight * scale;
                float marginX = pageWidth * 0.04f;
                float marginY = pageHeight * 0.04f;
                float x = pageWidth - targetWidth - marginX;
                float y = marginY;

                try {
                    BufferedImage pageImage = renderer.renderImageWithDPI(pageIndex, 110);
                    int width = pageImage.getWidth();
                    int height = pageImage.getHeight();
                    int denseRowThreshold = Math.max(20, (int) (width * 0.30f));
                    int denseColThreshold = Math.max(12, (int) (height * 0.06f));
                    int lastDenseRow = -1;
                    int rightDenseCol = -1;

                    for (int iy = 0; iy < height; iy++) {
                        int darkCount = 0;
                        for (int ix = 0; ix < width; ix++) {
                            int rgb = pageImage.getRGB(ix, iy);
                            int r = (rgb >> 16) & 0xff;
                            int g = (rgb >> 8) & 0xff;
                            int b = rgb & 0xff;
                            if (r < 120 || g < 120 || b < 120) {
                                darkCount++;
                            }
                        }
                        if (darkCount >= denseRowThreshold) {
                            lastDenseRow = iy;
                        }
                    }

                    for (int ix = 0; ix < width; ix++) {
                        int darkCount = 0;
                        for (int iy = 0; iy < height; iy++) {
                            int rgb = pageImage.getRGB(ix, iy);
                            int r = (rgb >> 16) & 0xff;
                            int g = (rgb >> 8) & 0xff;
                            int b = rgb & 0xff;
                            if (r < 120 || g < 120 || b < 120) {
                                darkCount++;
                            }
                        }
                        if (darkCount >= denseColThreshold) {
                            rightDenseCol = ix;
                        }
                    }

                    if (lastDenseRow >= 0 && rightDenseCol >= 0) {
                        float contentRight = (rightDenseCol + 1f) * pageWidth / width;
                        float contentBottom = pageHeight - ((lastDenseRow + 1f) * pageHeight / height);
                        float insetX = targetWidth * 0.06f;
                        float insetY = targetHeight * 0.06f;
                        float sealX = contentRight - targetWidth - insetX;
                        float sealY = contentBottom + insetY;
                        float maxX = pageWidth - targetWidth - marginX;
                        float maxY = pageHeight - targetHeight - marginY;
                        float minX = marginX;
                        float minY = marginY;
                        x = Math.max(minX, Math.min(sealX, maxX));
                        y = Math.max(minY, Math.min(sealY, maxY));
                    }
                } catch (Exception e) {
                    log.warn("Failed to detect table edge for seal placement on page {}", pageIndex + 1, e);
                }
                try (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    cs.drawImage(image, x, y, targetWidth, targetHeight);
                }
                pageIndex++;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Failed to add seal to PDF", e);
            return pdfBytes;
        }
    }

    public static byte[] addPageNumbers(byte[] pdfBytes) {
        byte[] fontBytes = getFontBytes();
        if (fontBytes == null) {
            return pdfBytes;
        }
        try (PDDocument doc = PDDocument.load(pdfBytes)) {
            PDFont font = loadFont(doc, fontBytes);
            if (font == null) {
                return pdfBytes;
            }
            int total = doc.getNumberOfPages();
            PDExtendedGraphicsState r0 = new PDExtendedGraphicsState();
            r0.setNonStrokingAlphaConstant(PAGE_NUMBER_ALPHA);
            int index = 1;
            for (PDPage page : doc.getPages()) {
                try (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    cs.setGraphicsStateParameters(r0);
                    cs.setFont(font, PAGE_NUMBER_FONT_SIZE);
                    float pageWidth = page.getMediaBox().getWidth();

                    float leftPaddingX = 28.35f;
                    float rightPaddingX = 18.0f;
                    float y = 24f;

                    cs.setNonStrokingColor(new Color(0x88, 0x88, 0x88));
                    cs.beginText();
                    cs.newLineAtOffset(leftPaddingX, y);
                    cs.showText("平台信息依实为准，信息使用自判");
                    cs.endText();

                    String pageLabel = "第 " + index + "/" + total + " 页";
                    float pageLabelWidth = font.getStringWidth(pageLabel) / 1000f * PAGE_NUMBER_FONT_SIZE;
                    float x = pageWidth - rightPaddingX - pageLabelWidth;
                    cs.setNonStrokingColor(Color.BLACK);
                    cs.beginText();
                    cs.newLineAtOffset(x, y);
                    cs.showText(pageLabel);
                    cs.endText();
                }
                index++;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            return pdfBytes;
        }
    }

    public static byte[] addImageTopLeft(byte[] pdfBytes, byte[] imageBytes) {
        return addImageTopLeft(pdfBytes, imageBytes, 0.18f, 0.04f);
    }

    public static byte[] addImageTopLeft(byte[] pdfBytes, byte[] imageBytes, float widthRatio, float marginRatio) {
        if (pdfBytes == null || imageBytes == null || imageBytes.length == 0) {
            return pdfBytes;
        }
        try (PDDocument doc = PDDocument.load(pdfBytes)) {
            PDImageXObject image = PDImageXObject.createFromByteArray(doc, imageBytes, "wm_top_left");
            float imageWidth = image.getWidth();
            float imageHeight = image.getHeight();
            for (PDPage page : doc.getPages()) {
                float pageWidth = page.getMediaBox().getWidth();
                float pageHeight = page.getMediaBox().getHeight();
                float targetWidth = pageWidth * widthRatio;
                float scale = targetWidth / imageWidth;
                float targetHeight = imageHeight * scale;
                float marginX = pageWidth * marginRatio;
                float marginY = pageHeight * marginRatio;
                float x = marginX;
                float y = pageHeight - targetHeight - marginY;
                try (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    cs.drawImage(image, x, y, targetWidth, targetHeight);
                }
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Failed to add top-left image to PDF", e);
            return pdfBytes;
        }
    }

    public static byte[] addImageTopRight(byte[] pdfBytes, byte[] imageBytes) {
        return addImageTopRight(pdfBytes, imageBytes, 0.18f, 0.04f);
    }

    public static byte[] addImageTopRight(byte[] pdfBytes, byte[] imageBytes, float widthRatio, float marginRatio) {
        if (pdfBytes == null || imageBytes == null || imageBytes.length == 0) {
            return pdfBytes;
        }
        try (PDDocument doc = PDDocument.load(pdfBytes)) {
            PDImageXObject image = PDImageXObject.createFromByteArray(doc, imageBytes, "wm_top_right");
            float imageWidth = image.getWidth();
            float imageHeight = image.getHeight();
            for (PDPage page : doc.getPages()) {
                float pageWidth = page.getMediaBox().getWidth();
                float pageHeight = page.getMediaBox().getHeight();
                float targetWidth = pageWidth * widthRatio;
                float scale = targetWidth / imageWidth;
                float targetHeight = imageHeight * scale;
                float marginX = pageWidth * marginRatio;
                float marginY = pageHeight * marginRatio;
                float x = pageWidth - targetWidth - marginX;
                float y = pageHeight - targetHeight - marginY;
                try (PDPageContentStream cs = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                    cs.drawImage(image, x, y, targetWidth, targetHeight);
                }
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("Failed to add top-right image to PDF", e);
            return pdfBytes;
        }
    }
}
