package com.ruoyi.common.utils.file;

import com.ruoyi.common.constant.Constants;
import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.poi.hslf.usermodel.HSLFSlide;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Iterator;

/**
 * @author fangk
 */
public class OfficeToPdfUtils {


    private static final Logger log = LoggerFactory.getLogger(OfficeToPdfUtils.class);

    public static void office2Pdf(String fileType, String inPath, String outPath) {
        try {
            if (Constants.FILE_TYPE_WORD.contains(fileType)) {
                wordToPdf(inPath, outPath);
            } else if (Constants.FILE_TYPE_EXCEL.contains(fileType)) {
                excelToPdf(inPath, outPath);
            } else if (Constants.FILE_TYPE_PPT.contains(fileType)) {
                pptToPdf(inPath, outPath);
            } else {
                // 不支持的文件类型
                log.error("不支持的文件类型");
                throw new RuntimeException("不支持的文件类型");
            }
        } catch (Exception e) {
            log.error("文件转换失败", e);
        }

    }


    /**
     * 将Word文档转换为PDF
     *
     * @param inputPath  Word文档的输入路径
     * @param outputPath 生成的PDF文件的输出路径
     * @throws Exception 如果转换过程中发生错误
     */
    public static void wordToPdf(String inputPath, String outputPath) throws Exception {
        if (inputPath.toLowerCase().endsWith(".docx")) {
            convertDocxToPdf(inputPath, outputPath);
        } else if (inputPath.toLowerCase().endsWith(".doc")) {
            convertDocToPdf(inputPath, outputPath);
        } else {
            throw new IllegalArgumentException("不支持的文件格式。请使用 .docx 或 .doc 文件。");
        }
    }

    private static void convertDocxToPdf(String inputPath, String outputPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(inputPath);
             XWPFDocument document = new XWPFDocument(fis);
             FileOutputStream fos = new FileOutputStream(outputPath)) {

            PdfOptions options = PdfOptions.create();
            PdfConverter.getInstance().convert(document, fos, options);
        } catch (IOException e) {
            throw new Exception("DOCX转PDF过程中发生错误: " + e.getMessage(), e);
        }
    }

    private static void convertDocToPdf(String inputPath, String outputPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(inputPath);
             HWPFDocument document = new HWPFDocument(fis);
             PDDocument pdfDocument = new PDDocument()) {

            WordExtractor extractor = new WordExtractor(document);
            String[] paragraphs = extractor.getParagraphText();

            PDPage page = new PDPage();
            pdfDocument.addPage(page);

            PDType0Font font = PDType0Font.load(pdfDocument, OfficeToPdfUtils.class.getResourceAsStream("/fonts/NotoSansSC-Regular.ttf"));
            PDPageContentStream contentStream = null;
            try {
                contentStream = new PDPageContentStream(pdfDocument, page);
                contentStream.setFont(font, 12);
                float y = 700;
                for (String paragraph : paragraphs) {
                    String[] lines = paragraph.split("\\r?\\n");
                    for (String line : lines) {
                        // 移除不可打印字符
                        line = line.replaceAll("\\p{C}", "");
                        if (!line.isEmpty()) {
                            contentStream.beginText();
                            contentStream.newLineAtOffset(50, y);
                            contentStream.showText(line);
                            contentStream.endText();
                            y -= 15;
                            if (y < 50) {
                                contentStream.close();
                                page = new PDPage();
                                pdfDocument.addPage(page);
                                contentStream = new PDPageContentStream(pdfDocument, page);
                                contentStream.setFont(font, 12);
                                y = 700;
                            }
                        }
                    }
                    // 段落间距
                    y -= 10;
                }
            } catch (IOException e) {
                throw new Exception("DOC转PDF过程中发生错误: " + e.getMessage(), e);
            } finally {
                if (contentStream != null) {
                    contentStream.close();
                }
            }

            pdfDocument.save(outputPath);
        } catch (IOException e) {
            throw new Exception("DOC转PDF过程中发生错误: " + e.getMessage(), e);
        }
    }


    /**
     * 将Excel文档转换为PDF
     *
     * @param inputPath  Excel文档的输入路径
     * @param outputPath 生成的PDF文件的输出路径
     * @throws Exception 如果转换过程中发生错误
     */
    public static void excelToPdf(String inputPath, String outputPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(inputPath);
             Workbook workbook = WorkbookFactory.create(fis);
             PDDocument pdfDocument = new PDDocument()) {

            PDType0Font font = PDType0Font.load(pdfDocument, OfficeToPdfUtils.class.getResourceAsStream("/fonts/NotoSansSC-Regular.ttf"));

            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                Sheet sheet = workbook.getSheetAt(i);
                PDPage page = new PDPage();
                pdfDocument.addPage(page);
                PDPageContentStream contentStream = null;
                try {
                    contentStream = new PDPageContentStream(pdfDocument, page);
                    contentStream.setFont(font, 12);

                    float margin = 50;
                    float yStart = page.getMediaBox().getHeight() - margin;
                    float tableWidth = page.getMediaBox().getWidth() - 2 * margin;
                    float yPosition = yStart;
                    float rowHeight = 20f;
                    float cellMargin = 5f;

                    // 遍历Excel的行和列
                    Iterator<Row> rowIterator = sheet.iterator();
                    while (rowIterator.hasNext()) {
                        Row row = rowIterator.next();
                        float xPosition = margin;

                        for (Cell cell : row) {
                            String text = getCellValueAsString(cell);
                            contentStream.beginText();
                            contentStream.newLineAtOffset(xPosition + cellMargin, yPosition - 15);
                            contentStream.showText(text);
                            contentStream.endText();
                            xPosition += tableWidth / row.getLastCellNum();
                        }

                        yPosition -= rowHeight;
                        if (yPosition <= margin) {
                            // 如果页面空间不足，创建新页面
                            page = new PDPage();
                            pdfDocument.addPage(page);
                            contentStream.close();
                            contentStream = new PDPageContentStream(pdfDocument, page);
                            contentStream.setFont(font, 12);
                            yPosition = yStart;
                        }
                    }
                } finally {
                    if (contentStream != null) {
                        contentStream.close();
                    }
                }
            }

            pdfDocument.save(outputPath);
        } catch (IOException e) {
            throw new Exception("Excel转PDF过程中发生错误: " + e.getMessage(), e);
        }
    }

    /**
     * 获取单元格的值并转换为字符串
     *
     * @param cell Excel单元格
     * @return 单元格的字符串值
     */
    private static String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue().toString();
                } else {
                    return String.valueOf(cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return "";
        }
    }


    /**
     * 将PPT文档转换为PDF
     *
     * @param inputPath PPT文档的输入路径
     * @param outputPath 生成的PDF文件的输出路径
     * @throws Exception 如果转换过程中发生错误
     */
    // public static void pptToPdf(String pptPath, String pdfPath) throws Exception {
    //     XMLSlideShow ppt = new XMLSlideShow(Files.newInputStream(Paths.get(pptPath)));
    //     Dimension pgsize = ppt.getPageSize();
    //
    //     // Increase resolution // Adjust this value to increase or decrease resolution
    //     float scale = 3f;
    //     int width = (int) (pgsize.width * scale);
    //     int height = (int) (pgsize.height * scale);
    //
    //     Path tempDir = Paths.get("temp_slides");
    //     Files.createDirectories(tempDir);
    //
    //     int slideIndex = 0;
    //     for (XSLFSlide slide : ppt.getSlides()) {
    //         BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    //         Graphics2D graphics = img.createGraphics();
    //
    //         // Set high quality rendering hints
    //         graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    //         graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    //         graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    //         graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    //
    //         // Scale the rendering
    //         graphics.scale(scale, scale);
    //
    //         // Clear the drawing area
    //         graphics.setPaint(Color.white);
    //         graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
    //
    //         // Render the slide
    //         slide.draw(graphics);
    //
    //         // Save each slide as a separate PDF
    //         PDDocument slidePdf = new PDDocument();
    //         PDPage page = new PDPage(new PDRectangle(pgsize.width, pgsize.height));
    //         slidePdf.addPage(page);
    //
    //         PDPageContentStream contentStream = new PDPageContentStream(slidePdf, page);
    //         contentStream.drawImage(LosslessFactory.createFromImage(slidePdf, img), 0, 0, pgsize.width, pgsize.height);
    //         contentStream.close();
    //
    //         // slidePdf.save(new File(tempDir, "slide_" + slideIndex + ".pdf"));
    //         Path slideFile = tempDir.resolve("slide_" + slideIndex + ".pdf");
    //         slidePdf.save(slideFile.toFile());
    //         slidePdf.close();
    //
    //         slideIndex++;
    //     }
    //
    //     // Merge all slide PDFs into one
    //     PDFMergerUtility pdfMerger = new PDFMergerUtility();
    //     for (int i = 0; i < slideIndex; i++) {
    //         // pdfMerger.addSource(new File(tempDir, "slide_" + i + ".pdf"));
    //         pdfMerger.addSource(tempDir.resolve("slide_" + i + ".pdf").toFile());
    //     }
    //     pdfMerger.setDestinationFileName(pdfPath);
    //     pdfMerger.mergeDocuments(null);
    //
    //     // 清理临时文件
    //     try (DirectoryStream<Path> stream = Files.newDirectoryStream(tempDir)) {
    //         for (Path file : stream) {
    //             Files.delete(file);
    //         }
    //     }
    //
    //     Files.delete(tempDir);
    // }


    public static void pptToPdf(String inputPath, String outputPath) throws Exception {
        Path input = Paths.get(inputPath);
        String fileName = input.getFileName().toString().toLowerCase();
        if (fileName.endsWith(".pptx")) {
            convertPptxToPdf(inputPath, outputPath);
        } else if (fileName.endsWith(".ppt")) {
            convertPptToPdf(inputPath, outputPath);
        } else {
            throw new IllegalArgumentException("Unsupported file format. Please provide a .ppt or .pptx file.");
        }
    }

    private static void convertPptxToPdf(String pptxPath, String pdfPath) throws Exception {
        try (XMLSlideShow ppt = new XMLSlideShow(Files.newInputStream(Paths.get(pptxPath)))) {
            Dimension pgsize = ppt.getPageSize();
            float scale = 3f;
            int width = (int) (pgsize.width * scale);
            int height = (int) (pgsize.height * scale);

            Path tempDir = createTempDirectory();

            int slideIndex = 0;
            for (XSLFSlide slide : ppt.getSlides()) {
                BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics2D graphics = createGraphics(img, scale, pgsize);

                slide.draw(graphics);

                saveSlideToPdf(tempDir, slideIndex, img, pgsize);
                slideIndex++;
            }

            mergePdfs(tempDir, slideIndex, pdfPath);
            cleanupTempFiles(tempDir);
        }
    }

    private static void convertPptToPdf(String pptPath, String pdfPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(pptPath);
             HSLFSlideShow ppt = new HSLFSlideShow(fis)) {
            Dimension pgsize = ppt.getPageSize();
            float scale = 3f;
            int width = (int) (pgsize.width * scale);
            int height = (int) (pgsize.height * scale);

            Path tempDir = createTempDirectory();

            int slideIndex = 0;
            for (HSLFSlide slide : ppt.getSlides()) {
                BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                Graphics2D graphics = createGraphics(img, scale, pgsize);

                slide.draw(graphics);

                saveSlideToPdf(tempDir, slideIndex, img, pgsize);
                slideIndex++;
            }

            mergePdfs(tempDir, slideIndex, pdfPath);
            cleanupTempFiles(tempDir);
        }
    }

    private static Path createTempDirectory() throws Exception {
        Path tempDir = Paths.get("temp_slides");
        Files.createDirectories(tempDir);
        return tempDir;
    }

    private static Graphics2D createGraphics(BufferedImage img, float scale, Dimension pgsize) {
        Graphics2D graphics = img.createGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        graphics.scale(scale, scale);
        graphics.setPaint(Color.white);
        graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
        return graphics;
    }

    private static void saveSlideToPdf(Path tempDir, int slideIndex, BufferedImage img, Dimension pgsize) throws Exception {
        try (PDDocument slidePdf = new PDDocument()) {
            PDPage page = new PDPage(new PDRectangle(pgsize.width, pgsize.height));
            slidePdf.addPage(page);

            try (PDPageContentStream contentStream = new PDPageContentStream(slidePdf, page)) {
                contentStream.drawImage(LosslessFactory.createFromImage(slidePdf, img), 0, 0, pgsize.width, pgsize.height);
            }

            Path slideFile = tempDir.resolve("slide_" + slideIndex + ".pdf");
            slidePdf.save(slideFile.toFile());
        }
    }

    private static void mergePdfs(Path tempDir, int slideCount, String pdfPath) throws Exception {
        PDFMergerUtility pdfMerger = new PDFMergerUtility();
        for (int i = 0; i < slideCount; i++) {
            pdfMerger.addSource(tempDir.resolve("slide_" + i + ".pdf").toFile());
        }
        pdfMerger.setDestinationFileName(pdfPath);
        pdfMerger.mergeDocuments(null);
    }

    private static void cleanupTempFiles(Path tempDir) throws Exception {
        Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}