package com.wmyun.farmwork.word.core;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import com.wmyun.farmwork.word.core.enums.BookmarkType;
import com.wmyun.farmwork.word.core.model.BookmarkInfo;
import com.wmyun.farmwork.word.core.model.BookmarkReplaceDataModel;
import com.wmyun.farmwork.word.core.model.TableBookmarkInfo;
import com.wmyun.farmwork.word.core.model.ext.ListExData;
import com.wmyun.farmwork.word.core.model.ext.PictureExData;
import com.wmyun.farmwork.word.core.model.ext.TableExData;
import com.wmyun.farmwork.word.core.model.ext.TextExData;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.stereotype.Component;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: word书签替换
 * @Date: 2025/3/3 10:10
 * @Created: by ZZSLL
 */

@Component
@Slf4j
public class BookmarkExec {

    public final static String tmpDir = System.getProperty("java.io.tmpdir") + File.separator;

    private static final String BOOKMARK_START = "w:bookmarkStart";
    private static final String BOOKMARK_END = "w:bookmarkEnd";
    private static final String MARK_PARAGRAPH = "w:pPr";
    private static final String MARK_RUN = "w:r";

    private static final String ATTR_BOOKMARK_ID = "w:id";
    private static final String ATTR_BOOKMARK_NAME = "w:name";

    private static final String TABLE_ROW_BOOKMARK_START = "w:colFirst";
    private static final String TABLE_ROW_BOOKMARK_END = "w:colLast";

    public static File replace(List<BookmarkReplaceDataModel> data, byte[] word, File file) {
        try (XWPFDocument doc = new XWPFDocument(IoUtil.toStream(word))) {

            // 收集文档中所有段落
            List<XWPFParagraph> allParagraphs = new ArrayList<>();
            collectionAllParagraphs(doc, allParagraphs);
            // 列表书签
            List<String> listMark = new ArrayList<>();
            // 表格内书签信息
            List<TableBookmarkInfo> tableBookmarkInfos = queryBookmarkInTable(doc);
            // 所有待替换数据
            Map<String, BookmarkReplaceDataModel> allDataMap = data.stream()
                    .collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));
            // 常规替换数据
            Map<String, BookmarkReplaceDataModel> dataMap = data.stream()
                    .filter(d -> {
                        List<String> bookmarks = tableBookmarkInfos.stream()
                                .flatMap(info -> info.getBookmark().stream())
                                .toList();
                        return !bookmarks.contains(d.getName());
                    }) // 过滤表格中需要整行替换的书签
                    .collect(Collectors.toMap(BookmarkReplaceDataModel::getName, b -> b));

            // 处理段落中书签
            for (XWPFParagraph paragraph : allParagraphs) {
                List<XWPFRun> runs = paragraph.getRuns();
                List<BookmarkInfo> info = queryBookmarkInfo(paragraph);
                for (BookmarkInfo bk : info) {
                    if (bk.isListMark()) listMark.add(bk.getBookmarkName());
                }
                Map<String, BookmarkInfo> bkMap = info.stream()
                        .collect(Collectors.toMap(BookmarkInfo::getBookmarkName, bk -> bk));

                List<Integer> waitRemoveIdx = new ArrayList<>();
                for (Map.Entry<String, BookmarkInfo> bk : bkMap.entrySet()) {
                    String name = bk.getKey();
                    BookmarkInfo bkInfo = bk.getValue();
                    if (!dataMap.containsKey(name)) {
                        continue;
                    }
                    BookmarkReplaceDataModel model = dataMap.get(name);
                    int startIdx = bkInfo.getStartIdx();
                    int endIdx = bkInfo.getEndIdx();
                    XWPFRun run = runs.get(startIdx);
                    CTR ctr = run.getCTR();
                    // 修改第一个run的内容
                    for (int i = 0; i < ctr.getTList().size(); i++) {
                        ctr.removeT(i);
                    }
                    CTText newTextNode = ctr.addNewT();
                    if (model.getExtData() instanceof TextExData exData) {
                        newTextNode.setStringValue(exData.getValue());
                    }

                    if (model.getExtData() instanceof PictureExData pictureData) {
                        BookmarkType type = pictureData.getType();
                        // 处理图片，以嵌入式插入图片
                        if (BookmarkType.PICTURE.equals(type)) {
                            try {
                                run.addPicture(new ByteArrayInputStream(pictureData.readAsByteArray()), pictureData.readPictureType(), pictureData.getPictureName(), Units.toEMU(pictureData.getWidth()), Units.toEMU(pictureData.getHeight()));
                            } catch (InvalidFormatException e) {
                                log.error(e.getMessage(), e);
                                throw new RuntimeException(e);
                            }
                        }
                    }

                    // 删除其余的run
                    for (int i = startIdx + 1; i < endIdx; i++) {
                        waitRemoveIdx.add(i);
                    }
                }

                waitRemoveIdx.sort(Comparator.naturalOrder());
                for (Integer idx : waitRemoveIdx) {
                    paragraph.removeRun(idx);
                }
            }

            // 处理列表
            for (String mk : listMark) {
                if (allDataMap.containsKey(mk)) {
                    BookmarkReplaceDataModel model = allDataMap.get(mk);
                    if (model.getExtData() instanceof ListExData exData) {
                        handleListContent(doc, exData.getValue(), mk, allDataMap);
                    }
                }
            }

            // 处理表格
            for (TableBookmarkInfo tableBkInfo : tableBookmarkInfos) {
                String masterBookmark = tableBkInfo.getMasterBookmark();
                if (allDataMap.containsKey(masterBookmark)) {
                    BookmarkReplaceDataModel model = allDataMap.get(masterBookmark);
                    if (model.getExtData() instanceof TableExData exData) {
                        handleTableContent(doc, tableBkInfo, exData.getValue());
                    }
                }
            }

            // 保存文件
            File outFile = FileUtil.newFile(tmpDir + "gen/" + FileNameUtil.getName(file));
            File parentDir = outFile.getParentFile();
            if (!parentDir.exists()) {
                parentDir.mkdirs();
            }
            try (FileOutputStream out = new FileOutputStream(outFile)) {
                doc.write(out);
            }
            return outFile;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 处理word中的表格，删除模板行，新增行
     */
    private static void handleTableContent(XWPFDocument doc, TableBookmarkInfo tableBkInfo, List<Map<String, String>> values) {
        List<XWPFTable> tables = doc.getTables();
        int tableIdx = tableBkInfo.getTableIdx();
        XWPFTable table = tables.get(tableIdx);
        int templateRowIdx = tableBkInfo.getTemplateRowIdx();

        // 获取模板行
        XWPFTableRow templateRow = table.getRow(templateRowIdx);

        // 提取各列的书签名称
        List<String> columnKeys = new ArrayList<>();
        for (XWPFTableCell cell : templateRow.getTableCells()) {
            String bookmarkName = null;
            // 遍历单元格的段落，查找第一个书签
            for (XWPFParagraph paragraph : cell.getParagraphs()) {
                for (CTBookmark bookmark : paragraph.getCTP().getBookmarkStartList()) {
                    bookmarkName = bookmark.getName();
                    break; // 取第一个书签
                }
                if (bookmarkName != null) {
                    break;
                }
            }
            if (bookmarkName == null) {
                throw new IllegalArgumentException("模板行的单元格中未找到书签");
            }
            columnKeys.add(bookmarkName);
        }

        // 插入新行并填充数据
        int currentInsertPos = templateRowIdx; // 初始插入位置为模板行的索引
        for (Map<String, String> dataRow : values) {
            // 在指定位置插入新行
            XWPFTableRow newRow = table.insertNewTableRow(currentInsertPos);
            // 复制模板行的每个单元格的格式并填充数据
            for (int i = 0; i < templateRow.getTableCells().size(); i++) {
                XWPFTableCell templateCell = templateRow.getCell(i);
                XWPFTableCell newCell = newRow.addNewTableCell();

                // 复制单元格样式
                CTTcPr templateTcPr = templateCell.getCTTc().getTcPr();
                if (templateTcPr != null) {
                    newCell.getCTTc().setTcPr((CTTcPr) templateTcPr.copy());
                }

                // 设置单元格内容
                String key = columnKeys.get(i);
                String value = dataRow.getOrDefault(key, "");
                // 清除单元格原有内容
                newCell.removeParagraph(0); // 移除默认创建的段落
                XWPFParagraph paragraph = newCell.addParagraph();
                XWPFRun run = paragraph.createRun();
                run.setText(value);
            }
            currentInsertPos++; // 插入位置后移
        }

        // 删除模板行（此时模板行的新索引为原索引加上插入的行数）
        int finalTemplateRowIndex = templateRowIdx + values.size();
        table.removeRow(finalTemplateRowIndex);
    }

    /**
     * 查询word中表格中需要整行替换的书签
     */
    private static List<TableBookmarkInfo> queryBookmarkInTable(XWPFDocument doc) {

        List<TableBookmarkInfo> list = new ArrayList<>();
        List<XWPFTable> tables = doc.getTables();

        // 表格
        for (XWPFTable table : tables) {
            int idx = tables.indexOf(table);
            TableBookmarkInfo info = new TableBookmarkInfo();
            List<String> filterMark = new ArrayList<>();

            // 行
            for (XWPFTableRow row : table.getRows()) {
                int currentRowIdx = table.getRows().indexOf(row);

                // 单元格
                for (XWPFTableCell cell : row.getTableCells()) {
                    boolean isReplaceRow = false;
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    for (XWPFParagraph paragraph : paragraphs) {
                        CTP ctp = paragraph.getCTP();
                        List<CTBookmark> bookmark = ctp.getBookmarkStartList();

                        // 单元格内书签
                        for (CTBookmark bk : bookmark) {
                            NamedNodeMap attr = bk.getDomNode().getAttributes();
                            Optional<Node> startAttrOpt = Optional.ofNullable(attr.getNamedItem(TABLE_ROW_BOOKMARK_START));
                            Optional<Node> endAttrOpt = Optional.ofNullable(attr.getNamedItem(TABLE_ROW_BOOKMARK_END));

                            // 是否是整行是书签，如果是，标记这一行为模板
                            if (startAttrOpt.map(Node::getNodeValue).isPresent() && endAttrOpt.map(Node::getNodeValue).isPresent()) {
                                filterMark.add(bk.getName());
                                isReplaceRow = true;
                                info.setTableIdx(idx);
                                info.setTemplateRowIdx(currentRowIdx);
                                info.setMasterBookmark(bk.getName());
                            }
                        }

                        // 收集模板行的所有书签
                        if (isReplaceRow) {
                            filterMark.addAll(bookmark.stream().map(CTBookmark::getName).collect(Collectors.toSet()));
                            info.setBookmark(filterMark);
                            list.add(info);
                        }
                    }
                }
            }
        }
        return list;
    }

    /**
     * 查询本段中的书签信息
     *
     * @param paragraph 段
     * @return List<BookmarkInfo>
     */
    private static List<BookmarkInfo> queryBookmarkInfo(XWPFParagraph paragraph) {
        Set<BookmarkInfo> set = new LinkedHashSet<>();
        CTP ctp = paragraph.getCTP();
        List<CTBookmark> startList = ctp.getBookmarkStartList();
        for (CTBookmark ctBookmark : startList) {
            set.add(new BookmarkInfo(ctBookmark.getName(), ctBookmark.getId().toString()));
        }

        for (BookmarkInfo info : set) {

            BigInteger numID = paragraph.getNumID();
            if (numID != null) {
                info.setListMark(true);
            }

            queryBookmarkIdx(paragraph, info);
        }

        return new ArrayList<>(set);
    }

    /**
     * 查找书签在段落中包含的位置
     */
    private static void queryBookmarkIdx(XWPFParagraph paragraph, BookmarkInfo info) {
        List<XWPFRun> runs = paragraph.getRuns();
        boolean foundStart = false;
        boolean foundEnd = false;

        // 倒序遍历每个Run，i为原列表的索引
        for (int i = runs.size() - 1; i >= 0; i--) {
            XWPFRun run = runs.get(i);
            CTR ctr = run.getCTR();
            Node node = ctr.getDomNode();
            Node currentNode = node.getPreviousSibling();
            // 递归查找前一个兄弟节点
            while (currentNode != null) {
                String nodeName = currentNode.getNodeName();

                if (MARK_RUN.equals(nodeName)) {
                    // 跨run，跳过
                    break;
                }

                if (BOOKMARK_START.equals(nodeName)) {
                    NamedNodeMap attrs = currentNode.getAttributes();
                    Node idNode = attrs.getNamedItem(ATTR_BOOKMARK_ID);
                    String id = idNode != null ? idNode.getNodeValue() : "";

                    // 匹配书签ID或名称
                    if (id.equals(info.getBookmarkId())) {
                        info.setStartIdx(i); // 记录当前Run的索引为起始位置
                        foundStart = true;
                        break;
                    }
                } else if (BOOKMARK_END.equals(nodeName)) {
                    NamedNodeMap attrs = currentNode.getAttributes();
                    Node idNode = attrs.getNamedItem(ATTR_BOOKMARK_ID);
                    String id = idNode != null ? idNode.getNodeValue() : "";

                    // 匹配书签ID
                    if (id.equals(info.getBookmarkId())) {
                        info.setEndIdx(i); // 记录当前Run的索引为结束位置
                        foundEnd = true;
                        break;
                    }
                }
                currentNode = currentNode.getPreviousSibling(); // 继续查找前一个节点
            }

            // 如果已找到起始和结束，提前退出循环
            if (foundStart && foundEnd) {
                break;
            }
        }

        // 特殊处理书签结尾在段末尾的请情况，此时通过run的前一个节点获取不到end
        if (!foundEnd) {
            info.setEndIdx(runs.size());
        }
    }

    /**
     * 获取文档中所有段落
     */
    private static void collectionAllParagraphs(XWPFDocument doc, List<XWPFParagraph> allParagraphs) {

        // 1. 正文中段落
        allParagraphs.addAll(doc.getParagraphs());
        // 2. 正文中表格内的段落
        for (XWPFTable table : doc.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    allParagraphs.addAll(cell.getParagraphs());
                }
            }
        }

        // 3. 页眉中的段落及页眉内表格中的段落
        for (XWPFHeader header : doc.getHeaderList()) {
            allParagraphs.addAll(header.getParagraphs());
            for (XWPFTable table : header.getTables()) {
                for (XWPFTableRow row : table.getRows()) {
                    for (XWPFTableCell cell : row.getTableCells()) {
                        allParagraphs.addAll(cell.getParagraphs());
                    }
                }
            }
        }

        // 4. 页脚中的段落及页脚内表格中的段落
        for (XWPFFooter footer : doc.getFooterList()) {
            allParagraphs.addAll(footer.getParagraphs());
            for (XWPFTable table : footer.getTables()) {
                for (XWPFTableRow row : table.getRows()) {
                    for (XWPFTableCell cell : row.getTableCells()) {
                        allParagraphs.addAll(cell.getParagraphs());
                    }
                }
            }
        }
    }

    /**
     * 处理列表行新增
     */
    public static void handleListContent(XWPFDocument doc, List<String> newListItems, String bookmarkName, Map<String, BookmarkReplaceDataModel> allDataMap) {
        // 查找书签所在的段落
        XWPFParagraph bookmarkParagraph = findBookmarkParagraph(doc, bookmarkName);
        if (bookmarkParagraph == null) {
            throw new IllegalArgumentException("书签 '" + bookmarkName + "' 未找到");
        }

        // 获取编号属性
        CTNumPr ctNumPr = getParagraphNumPr(bookmarkParagraph);
        BigInteger numId = ctNumPr.getNumId().getVal();
        BigInteger ilvl = ctNumPr.getIlvl().getVal();

        // 插入新的列表项
        insertNewListItems(doc, newListItems, numId, ilvl, bookmarkParagraph, bookmarkName);
        // 删除原列表段落
        removeOriginalListParagraphs(doc, bookmarkName, allDataMap);

    }

    private static void removeOriginalListParagraphs(XWPFDocument doc, String bookmarkName, Map<String, BookmarkReplaceDataModel> allDataMap) {
        List<XWPFParagraph> paragraphs = doc.getParagraphs();
        List<Integer> indexesToRemove = new ArrayList<>();

        for (int i = 0; i < paragraphs.size(); i++) {
            XWPFParagraph paragraph = paragraphs.get(i);
            CTP ctp = paragraph.getCTP();
            List<CTBookmark> bookmarks = ctp.getBookmarkStartList();
            List<BookmarkInfo> info = queryBookmarkInfo(paragraph);
            Map<String, BookmarkInfo> bkMap = info.stream()
                    .collect(Collectors.toMap(BookmarkInfo::getBookmarkName, bk -> bk));
            for (CTBookmark bookmark : bookmarks) {
                if (bookmark.getName().equals(bookmarkName)) {
                    if (bkMap.containsKey(bookmarkName) && bkMap.get(bookmarkName).isListMark()) {
                        BookmarkReplaceDataModel model = allDataMap.get(bookmarkName);
                        if (model.getExtData() instanceof ListExData) {
                            indexesToRemove.add(i);
                            break;
                        }
                    }
                }
            }
        }

        Collections.reverse(indexesToRemove);
        for (int index : indexesToRemove) {
            doc.removeBodyElement(doc.getBodyElements().indexOf(paragraphs.get(index)));
        }
    }

    private static XWPFParagraph findBookmarkParagraph(XWPFDocument doc, String bookmarkName) {
        for (XWPFParagraph p : doc.getParagraphs()) {
            for (CTBookmark bookmark : p.getCTP().getBookmarkStartList()) {
                if (bookmarkName.equals(bookmark.getName())) {
                    return p;
                }
            }
        }
        return null;
    }

    private static CTNumPr getParagraphNumPr(XWPFParagraph paragraph) {
        CTPPr pPr = paragraph.getCTP().getPPr();
        if (pPr == null) {
            throw new IllegalStateException("段落无PPr属性");
        }
        CTNumPr numPr = pPr.getNumPr();
        if (numPr == null) {
            throw new IllegalStateException("段落不属于编号列表");
        }
        return numPr;
    }

    private static void insertNewListItems(XWPFDocument doc, List<String> items,
                                           BigInteger numId, BigInteger ilvl, XWPFParagraph originalPara, String bookmarkName) {
        // 获取插入位置的游标锚点
        XmlCursor cursor = findInsertCursor(doc, bookmarkName);

        // 循环插入新列表项
        for (String item : items) {
            // 插入新段落
            XWPFParagraph newPara = doc.insertNewParagraph(cursor);

            // 复制原样式和列表格式
            copyParagraphStyle(originalPara, newPara);
            applyListFormatting(newPara, numId, ilvl);
            // 设置文本内容
            XWPFRun run = newPara.createRun();

            run.setText(item);
            // 将游标移动到新段落之后
            cursor = newPara.getCTP().newCursor();
            cursor.toNextSibling();
        }
    }

    private static XmlCursor findInsertCursor(XWPFDocument doc, String bookmarkName) {
        List<XWPFParagraph> paragraphs = doc.getParagraphs();

        for (XWPFParagraph paragraph : paragraphs) {
            CTP ctp = paragraph.getCTP();
            List<CTBookmark> bookmarks = ctp.getBookmarkStartList();
            for (CTBookmark bookmark : bookmarks) {
                if (bookmarkName.equals(bookmark.getName())) {
                    XmlCursor cursor = paragraph.getCTP().newCursor();
                    cursor.toNextSibling();
                    return cursor;
                }
            }
        }
        throw new IllegalArgumentException("书签 '" + bookmarkName + "' 未找到");
    }

    private static void copyParagraphStyle(XWPFParagraph source, XWPFParagraph target) {
        CTPPr sourcePPr = source.getCTP().getPPr();
        if (sourcePPr != null) {
            target.getCTP().setPPr((CTPPr) sourcePPr.copy());
        }
    }

    private static void applyListFormatting(XWPFParagraph para, BigInteger numId, BigInteger ilvl) {
        CTPPr pPr = para.getCTP().getPPr();
        CTNumPr numPr = pPr.getNumPr();
        if (numPr == null) {
            numPr = pPr.addNewNumPr();
        }
        numPr.getIlvl().setVal(ilvl);
        numPr.getNumId().setVal(numId);
    }
}
