package org.dromara.main.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.*;
import org.dromara.common.core.exception.ServiceException;

import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Word 模板报告服务。
 * <p>
 * 从 classpath 加载 Word 模板 (templates/report_template.docx)，
 * 将 {{placeholder}} 占位符替换为实际值，
 * 处理 checkbox 勾选框（Wingdings 字体），
 * 然后通过 LibreOffice headless 转换为高保真 PDF。
 */
@Slf4j
@Component
public class WordReportService {

    private static final String TEMPLATE_PATH = "templates/report_template.docx";
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{\\s*(\\w+)\\s*}}");
    /** CB_ 前缀的占位符，表示 checkbox 组，值格式为 "选项1|选项2|选项3|SELECTED:选中项" */
    private static final Pattern CB_PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{CB_(\\w+)}}");
    private static final String DEBUG_DOCX_PATH = "report_debug_output.docx";

    // Wingdings 字符：选中 ☑ = char 254 (0xFE), 未选中 ☐ = char 168 (0xA8)
    private static final char WINGDINGS_CHECKED = (char) 0xFE;
    private static final char WINGDINGS_UNCHECKED = (char) 0xA8;

    private static final String[] SOFFICE_PATHS = {
        "C:\\Program Files\\LibreOffice\\program\\soffice.exe",
        "C:\\Program Files (x86)\\LibreOffice\\program\\soffice.exe",
        "D:\\Program Files\\LibreOffice\\program\\soffice.exe",
        "/usr/bin/soffice",
        "/usr/local/bin/soffice",
    };

    /**
     * 根据占位符映射生成 PDF。
     *
     * @param placeholders key=占位符名称, value=要填入的值。
     *                     对于 CB_ 前缀的 key，value 格式为 "选项1,选项2,选项3|SELECTED:选中项"
     * @return PDF 文件字节数组
     */
    public byte[] generatePdf(Map<String, String> placeholders) {
        try (InputStream templateStream = new ClassPathResource(TEMPLATE_PATH).getInputStream();
             XWPFDocument document = new XWPFDocument(templateStream)) {

            // 1. 替换文档正文段落中的占位符
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                replacePlaceholdersInParagraph(paragraph, placeholders);
            }

            // 2. 替换所有表格中的占位符（包括 checkbox）
            for (XWPFTable table : document.getTables()) {
                for (XWPFTableRow row : table.getRows()) {
                    for (XWPFTableCell cell : row.getTableCells()) {
                        for (XWPFParagraph paragraph : new ArrayList<>(cell.getParagraphs())) {
                            // 检查是否是 checkbox 占位符
                            String paraText = paragraph.getText();
                            if (paraText != null) {
                                Matcher cbMatcher = CB_PLACEHOLDER_PATTERN.matcher(paraText);
                                if (cbMatcher.find()) {
                                    String cbKey = "CB_" + cbMatcher.group(1);
                                    String cbValue = placeholders.getOrDefault(cbKey, "");
                                    applyCheckboxParagraph(cell, paragraph, cbValue);
                                    continue;
                                }
                            }
                            replacePlaceholdersInParagraph(paragraph, placeholders);
                        }
                    }
                }
            }

            // 3. 保存到临时文件
            Path tempDir = Files.createTempDirectory("report_");
            Path docxPath = tempDir.resolve("report.docx");
            try (FileOutputStream fos = new FileOutputStream(docxPath.toFile())) {
                document.write(fos);
            }

            // 调试：保存到项目目录
            saveDebugDocx(document);

            // 4. LibreOffice 转 PDF
            byte[] pdfBytes = convertToPdfWithLibreOffice(docxPath, tempDir);

            // 5. 清理
            cleanupTempDir(tempDir);

            return pdfBytes;
        } catch (ServiceException e) {
            log.error("报告 PDF 生成失败: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("报告 PDF 生成异常", e);
            throw new ServiceException("生成报告PDF失败: " + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()));
        }
    }

    /**
     * 处理 checkbox 组。
     * value 格式: "选项1,选项2,选项3|SELECTED:选中项"
     * 例如: "属实,不属实,无法核实|SELECTED:属实"
     *
     * 会在 cell 中生成多行，每行一个 checkbox + 标签，
     * 选中项使用 Wingdings ☑ (char 254)，未选中使用 ☐ (char 168)。
     */
    private void applyCheckboxParagraph(XWPFTableCell cell, XWPFParagraph existingParagraph, String value) {
        if (value == null || value.isEmpty()) {
            // 清空占位符文本
            clearParagraphText(existingParagraph);
            return;
        }

        // 解析 value: "选项1,选项2,选项3|SELECTED:选中项"
        String optionsPart = value;
        String selected = "";
        if (value.contains("|SELECTED:")) {
            int idx = value.indexOf("|SELECTED:");
            optionsPart = value.substring(0, idx);
            selected = value.substring(idx + "|SELECTED:".length());
        }
        String[] options = optionsPart.split(",");

        // 清空现有段落
        clearParagraphText(existingParagraph);

        // 在第一个选项写入现有段落
        boolean isFirst = true;
        for (String option : options) {
            option = option.trim();
            boolean isChecked = option.equals(selected.trim());

            XWPFParagraph para;
            if (isFirst) {
                para = existingParagraph;
                isFirst = false;
            } else {
                para = cell.addParagraph();
                // 复制段落格式
                if (existingParagraph.getCTP().getPPr() != null) {
                    para.getCTP().setPPr(existingParagraph.getCTP().getPPr());
                }
            }

            // 创建 Wingdings checkbox run
            XWPFRun checkRun = para.createRun();
            setWingdingsFont(checkRun);
            checkRun.setText(String.valueOf(isChecked ? WINGDINGS_CHECKED : WINGDINGS_UNCHECKED));

            // 创建标签 run（普通字体）
            XWPFRun labelRun = para.createRun();
            labelRun.setText(" " + option);
            // 复制字号等格式
            if (existingParagraph.getRuns() != null && !existingParagraph.getRuns().isEmpty()) {
                XWPFRun origRun = existingParagraph.getRuns().get(0);
                if (origRun.getFontSizeAsDouble() != null) {
                    labelRun.setFontSize(origRun.getFontSizeAsDouble());
                    checkRun.setFontSize(origRun.getFontSizeAsDouble());
                }
            }
        }
    }

    /**
     * 为 run 设置 Wingdings 字体。
     */
    private void setWingdingsFont(XWPFRun run) {
        run.setFontFamily("Wingdings");
    }

    /**
     * 清空段落中所有 run 的文本。
     */
    private void clearParagraphText(XWPFParagraph paragraph) {
        List<XWPFRun> runs = paragraph.getRuns();
        if (runs != null) {
            for (XWPFRun run : runs) {
                setRunTextSafely(run, "");
            }
        }
    }

    /**
     * 替换段落中的普通 {{placeholder}} 占位符。
     */
    private void replacePlaceholdersInParagraph(XWPFParagraph paragraph, Map<String, String> placeholders) {
        String fullText = paragraph.getText();
        if (fullText == null || !fullText.contains("{{")) {
            return;
        }

        List<XWPFRun> runs = paragraph.getRuns();
        if (runs == null || runs.isEmpty()) {
            return;
        }

        StringBuilder sb = new StringBuilder();
        for (XWPFRun run : runs) {
            String runText = run.getText(0);
            if (runText != null) {
                sb.append(runText);
            }
        }

        String concatenated = sb.toString();
        Matcher matcher = PLACEHOLDER_PATTERN.matcher(concatenated);
        if (!matcher.find()) {
            return;
        }

        String replaced = PLACEHOLDER_PATTERN.matcher(concatenated).replaceAll(mr -> {
            String key = mr.group(1).trim();
            String value = placeholders.getOrDefault(key, " ");
            return value.isEmpty() ? " " : Matcher.quoteReplacement(value);
        });

        if (replaced.contains("\n")) {
            String[] lines = replaced.split("\n", -1);
            setRunTextSafely(runs.get(0), lines[0]);
            for (int i = 1; i < runs.size(); i++) {
                setRunTextSafely(runs.get(i), "");
            }
            for (int i = 1; i < lines.length; i++) {
                runs.get(0).addBreak();
                runs.get(0).setText(lines[i]);
            }
        } else {
            setRunTextSafely(runs.get(0), replaced);
            for (int i = 1; i < runs.size(); i++) {
                setRunTextSafely(runs.get(i), "");
            }
        }
    }

    private void setRunTextSafely(XWPFRun run, String text) {
        if (run == null) {
            return;
        }
        try {
            if (run.getCTR() != null && run.getCTR().sizeOfTArray() > 0) {
                run.setText(text, 0);
            } else {
                run.setText(text);
            }
        } catch (Exception e) {
            log.warn("设置段落文本失败，回退为空 run: {}", e.getMessage(), e);
            try {
                run.setText(text);
            } catch (Exception ignored) {
                // 忽略二次失败，避免中断整体流程
            }
        }
    }

    // ==================== LibreOffice PDF 转换 ====================

    private byte[] convertToPdfWithLibreOffice(Path docxPath, Path outputDir) {
        String sofficePath = findSoffice();
        try {
            ProcessBuilder pb = new ProcessBuilder(
                sofficePath, "--headless", "--norestore",
                "--convert-to", "pdf",
                "--outdir", outputDir.toAbsolutePath().toString(),
                docxPath.toAbsolutePath().toString()
            );
            pb.redirectErrorStream(true);
            log.info("执行 LibreOffice 转 PDF: {}", String.join(" ", pb.command()));

            Process process = pb.start();
            String output;
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                output = reader.lines().reduce("", (a, b) -> a + "\n" + b);
            }

            int exitCode = process.waitFor();
            if (exitCode != 0) {
                log.error("LibreOffice 转换失败, exitCode={}, output={}", exitCode, output);
                throw new ServiceException("LibreOffice 转 PDF 失败 (exitCode=" + exitCode + ")");
            }
            log.info("LibreOffice 转换完成: {}", output.trim());

            Path pdfPath = outputDir.resolve("report.pdf");
            if (!Files.exists(pdfPath)) {
                try (var stream = Files.list(outputDir)) {
                    pdfPath = stream.filter(p -> p.toString().endsWith(".pdf"))
                        .findFirst()
                        .orElseThrow(() -> new ServiceException("LibreOffice 转换后未找到 PDF 文件"));
                }
            }
            byte[] pdfBytes = Files.readAllBytes(pdfPath);
            log.info("生成 PDF 大小: {} bytes", pdfBytes.length);
            return pdfBytes;
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            throw new ServiceException("调用 LibreOffice 转 PDF 失败: " + e.getMessage());
        }
    }

    private String findSoffice() {
        try {
            Process which = new ProcessBuilder("where", "soffice").start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(which.getInputStream()))) {
                String path = reader.readLine();
                if (path != null && !path.isBlank() && new File(path.trim()).exists()) {
                    return path.trim();
                }
            }
        } catch (Exception ignored) {}

        for (String path : SOFFICE_PATHS) {
            if (new File(path).exists()) {
                return path;
            }
        }
        throw new ServiceException("未找到 LibreOffice，请安装后重试。");
    }

    private void cleanupTempDir(Path tempDir) {
        try {
            try (var stream = Files.list(tempDir)) {
                stream.forEach(p -> { try { Files.deleteIfExists(p); } catch (Exception ignored) {} });
            }
            Files.deleteIfExists(tempDir);
        } catch (Exception e) {
            log.warn("清理临时目录失败: {}", e.getMessage());
        }
    }

    private void saveDebugDocx(XWPFDocument document) {
        try {
            File debugFile = new File(DEBUG_DOCX_PATH).getAbsoluteFile();
            try (FileOutputStream fos = new FileOutputStream(debugFile)) {
                document.write(fos);
            }
            log.info("填充后的 Word 已保存到: {}", debugFile.getAbsolutePath());
        } catch (Exception e) {
            log.warn("保存调试 Word 文件失败: {}", e.getMessage());
        }
    }
}
