package com.gxwebsoft.ai.controller;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.ai.entity.AuditEvidence;
import com.gxwebsoft.ai.mapper.AuditEvidenceMapper;
import com.gxwebsoft.ai.service.AuditEvidenceService;
import com.gxwebsoft.pwl.entity.PwlProject;
import com.gxwebsoft.pwl.service.PwlProjectService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.xwpf.usermodel.BreakType;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson.JSONObject;
import com.gxwebsoft.ai.config.TemplateConfig;
import com.gxwebsoft.ai.dto.AuditReportRequest;
import com.gxwebsoft.ai.dto.AuditReportSaveRequest;
import com.gxwebsoft.ai.dto.KnowledgeBaseRequest;
import com.gxwebsoft.ai.dto.AuditReportDefaultTextRequest;
import com.gxwebsoft.ai.entity.AuditReport;
import com.gxwebsoft.ai.enums.AuditReportEnum;
import com.gxwebsoft.ai.service.AuditReportService;
import com.gxwebsoft.ai.service.KnowledgeBaseService;
import com.gxwebsoft.ai.service.AiHistoryService;
import com.gxwebsoft.ai.util.AuditReportUtil;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.system.entity.User;

import cn.afterturn.easypoi.word.WordExportUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * 审计报告控制器
 * @author GIIT-YC
 *
 */
@Tag(name = "审计报告")
@RestController
@RequestMapping("/api/ai/auditReport")
@RequiredArgsConstructor
@Slf4j
public class AuditReportController extends BaseController {
    
    private final TemplateConfig templateConfig;

    private final KnowledgeBaseService knowledgeBaseService;

    private final AuditReportService auditReportService;

    private final PwlProjectService pwlProjectService;

    private final AuditEvidenceMapper auditEvidenceMapper;

    private final AuditEvidenceService auditEvidenceService;

    private final AiHistoryService aiHistoryService;

    private final static String AUDIT_REPORT_TOKEN = "Bearer app-PlYMOvcUQrW7tromrqCNcX9M";

//    private final AiCloudFileService aiCloudFileService;
//
//    private final AiCloudDocService aiCloudDocService;

//    private String invok(String query, String knowledge, String history, String suggestion, String title, String userName) {
//        // 构建请求体
//        JSONObject requestBody = new JSONObject();
//        JSONObject inputs = new JSONObject();
//        inputs.put("query", query);
//        inputs.put("knowledge", knowledge);
//        inputs.put("history", history);
//        inputs.put("suggestion", suggestion);
//        inputs.put("title", title);
//
//        requestBody.put("inputs", inputs);
//        requestBody.put("response_mode", "blocking");
//        requestBody.put("user", userName);
//
//        // 发送 POST 请求
//        String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run")
//                .header("Authorization", AUDIT_REPORT_TOKEN)
//                .header("Content-Type", "application/json")
//                .body(requestBody.toString())
//                .timeout(600000)
//                .execute()
//                .body();
//        // 解析返回的JSON字符串
//        JSONObject jsonResponse = JSONObject.parseObject(result);
//        // 获取data字段
//        JSONObject data = jsonResponse.getJSONObject("data");
//        // 获取outputs字段
//        JSONObject outputs = data.getJSONObject("outputs");
//        // 获取outputs中的result字符串
//        return outputs.getString("result");
//    }
    
    /**
     * 生成审计报告-单一模块
     */
    @Deprecated
    @Operation(summary = "生成审计报告-单一模块")
    @PostMapping("/generate")
    public ApiResult<?> generateAuditReport(@RequestBody AuditReportRequest req) {
    	final User loginUser = getLoginUser();
    	
    	Set<String> kbIdSet = new HashSet<>();
    	List<String> kbIdList = StrUtil.split(req.getKbIds(), ',');
    	for(String kbId : kbIdList) {
    		KnowledgeBaseRequest knowledgeBaseRequest = new KnowledgeBaseRequest();
    		knowledgeBaseRequest.setKbId(kbId);
    		//召回切片数上限
    		knowledgeBaseRequest.setTopK(100);
    		knowledgeBaseRequest.setFormCommit((req.getFormCommit() >= 10) ? req.getFormCommit() / 10 : req.getFormCommit());
    		kbIdSet.addAll(knowledgeBaseService.queryKnowledgeBase(knowledgeBaseRequest));
    	}
    	String knowledge = JSON.toJSONString(kbIdSet);
    	
    	String query = AuditReportEnum.getByCode(req.getFormCommit()).getDesc();
//    	String ret = this.invok(query, knowledge, AuditReportUtil.generateReportContent(req), req.getSuggestion(), loginUser.getUsername());
//    	String ret = this.invok(query, knowledge, AuditReportUtil.generateReportContentByFormCommit(req), req.getSuggestion(), req.getFrom00(), loginUser.getUsername());

    	return success(invokeAIAnalysis(query, knowledge, loginUser.getUsername()));
    }
    
    /**
     * 生成并下载审计报告
     */
    @Operation(summary = "生成并下载审计报告")
    @PostMapping("/download")
    public void downloadAuditReport(@RequestBody AuditReportRequest req, HttpServletResponse response) {
        double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();

        try {
            ZipSecureFile.setMinInflateRatio(0.001);

            // 准备模板数据
            Map<String, Object> map = new HashMap<>();
            map.put(AuditReportEnum.TITLE.getCodeStr(), req.getFrom00());
            map.put(AuditReportEnum.BASIS.getCodeStr(), req.getFrom10());
            map.put(AuditReportEnum.OBJECTIVE.getCodeStr(), req.getFrom20());
            map.put(AuditReportEnum.SCOPE.getCodeStr(), req.getFrom30());
            map.put(AuditReportEnum.UNIT_OVERVIEW.getCodeStr(), req.getFrom41());
            map.put(AuditReportEnum.ORG_PERSONNEL.getCodeStr(), req.getFrom42());
            map.put(AuditReportEnum.FINANCIAL_ACCOUNTING.getCodeStr(), req.getFrom43());
            map.put(AuditReportEnum.ANNUAL_BUSINESS.getCodeStr(), req.getFrom44());
            map.put(AuditReportEnum.INTERNAL_CONTROL.getCodeStr(), req.getFrom45());
            map.put(AuditReportEnum.ECONOMIC_POLICIES.getCodeStr(), req.getFrom51());
            map.put(AuditReportEnum.DEV_STRATEGY.getCodeStr(), req.getFrom52());
            map.put(AuditReportEnum.MAJOR_ECONOMIC.getCodeStr(), req.getFrom53());
            map.put(AuditReportEnum.CORP_GOVERNANCE.getCodeStr(), req.getFrom54());
            map.put(AuditReportEnum.FINANCIAL_LEGAL.getCodeStr(), req.getFrom55());
            map.put(AuditReportEnum.INTEGRITY_COMPLIANCE.getCodeStr(), req.getFrom56());
            map.put(AuditReportEnum.PREV_AUDIT_ISSUES.getCodeStr(), req.getFrom57());
            map.put(AuditReportEnum.OTHER_MATTERS.getCodeStr(), req.getFrom58());
            map.put(AuditReportEnum.RISK_IDENTIFY.getCodeStr(), req.getFrom61());
            map.put(AuditReportEnum.RISK_RESPONSE.getCodeStr(), req.getFrom62());
            map.put(AuditReportEnum.TECHNIQUES.getCodeStr(), req.getFrom70());
            map.put(AuditReportEnum.SCHEDULE.getCodeStr(), req.getFrom80());
            map.put(AuditReportEnum.ORGANIZATION.getCodeStr(), req.getFrom90());

            // 使用 Easypoi 的 Word 模板功能
            XWPFDocument document = WordExportUtil.exportWord07(templateConfig.getWordTemplatePath(), map);

            // 处理换行，确保 \n 转为硬回车（新段落）
            processParagraphs(document);

            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            response.setHeader("Content-Disposition", "attachment; filename=audit_report.docx");

            try (OutputStream out = response.getOutputStream()) {
                document.write(out);
                out.flush();
            }

        } catch (Exception e) {
            throw new RuntimeException("生成审计报告失败", e);
        } finally {
            ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
        }
    }

    /**
     * 根据项目 ID 查询审计报告和取证单数据
     */
    @Operation(summary = "根据项目 ID 查询审计数据")
    @PostMapping("/queryAuditDataByProjectId")
    public ApiResult<Map<String, Object>> queryAuditDataByProjectId(
            @RequestParam Integer projectId,
            @RequestBody(required = false) Map<String, Object> requestBody) {
        try {
            log.info("根据项目 ID 查询审计数据和取证单数据 - 项目 ID: {}, 请求体：{}", projectId, requestBody);
            
            // 从请求体中获取选中的取证单 ID 列表
            List<Long> evidenceIds = new ArrayList<>();
            if (requestBody != null && requestBody.containsKey("evidenceIds")) {
                Object evidenceIdsObj = requestBody.get("evidenceIds");
                if (evidenceIdsObj instanceof List) {
                    List<?> evidenceIdList = (List<?>) evidenceIdsObj;
                    for (Object id : evidenceIdList) {
                        if (id instanceof Integer) {
                            evidenceIds.add(((Integer) id).longValue());
                        } else if (id instanceof Long) {
                            evidenceIds.add((Long) id);
                        } else if (id instanceof Number) {
                            evidenceIds.add(((Number) id).longValue());
                        }
                    }
                    log.info("提取到勾选取证单 IDs: {}", evidenceIds);
                }
            }
            
            // 调用 Service 方法，传入证据 ID 列表
            PwlProject project = pwlProjectService.getById(projectId);
            Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds);
            return success(data);
        } catch (Exception e) {
            log.error("查询审计数据异常", e);
            return fail("查询审计数据异常：" + e.getMessage(), null);
        }
    }

    /**
     * 根据项目 ID 生成并下载审计报告（从数据库查询所有数据）
     */
    @Operation(summary = "根据项目 ID 生成审计报告")
    @PostMapping("/generateByProjectId")
    public void generateAuditReportByProjectId(
            @RequestParam Integer projectId,
            HttpServletResponse response) {
        double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();

        try {
            ZipSecureFile.setMinInflateRatio(0.001);

            // 1. 查询该项目的所有审计数据（包括报告和取证单），并对所有取证单进行 AI 分析
            List<AuditEvidence> allEvidences = new ArrayList<>();
            LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
            evidenceWrapper.eq(AuditEvidence::getProjectId, projectId)
                          .eq(AuditEvidence::getDeleted, 0);
            allEvidences = auditEvidenceMapper.selectList(evidenceWrapper);
            
            log.info("=== 生成 Word 报告 ===");
            log.info("项目 ID: {}", projectId);
            log.info("查询到的取证单总数：{}", allEvidences.size());
            
            // 提取所有证据 ID
            List<Long> allEvidenceIds = allEvidences.stream()
                .map(AuditEvidence::getId)
                .collect(Collectors.toList());
            
            log.info("勾选取证单 IDs: {}", allEvidenceIds);
            
            // 调用带分析的接口
            PwlProject project = pwlProjectService.getById(projectId);
            Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, allEvidenceIds);
            List<AuditReport> reports = (List<AuditReport>) data.get("reports");
            String evaluate = (String) data.get("evaluate");  // 审计总体评价
            String suggestion = (String) data.get("suggestion");  // 改进意见和建议
            
            log.info("=== Service 返回的数据 ===");
            log.info("evaluate 字数：{}", evaluate != null ? evaluate.length() : 0);
            log.info("suggestion 字数：{}", suggestion != null ? suggestion.length() : 0);
            log.info("evaluate 内容：{}", evaluate);
            log.info("suggestion 内容：{}", suggestion);
            
            // 2. 构建内容映射
            Map<Integer, String> contentMap = new HashMap<>();
            for (AuditReport report : reports) {
                if (report.getFormCommit() != null && StrUtil.isNotBlank(report.getReportContent())) {
                    String content = extractTextFromReportContent(report.getReportContent());
//                    log.info("formCommit={}, 提取的内容={}", report.getFormCommit(), content);
                    contentMap.put(report.getFormCommit(), content);
                }
            }
            
            // 3. 获取项目信息
            String projectName = project.getName() != null ? project.getName() : "";
            
            // 4. 使用 XWPFDocument 直接生成 Word 文档（不使用模板）
            XWPFDocument document = new XWPFDocument();
            
            // 5. 添加封面页
            addCoverPage(document, project);
            
            // 添加分页符
            addPageBreak(document);
            
            // 6. 添加正文标题（居中）
            addBodyTitle(document, "经济责任审计报告");
            
            // 7. 添加文号（右对齐，蓝色）- 从 pwl_project.case_index 获取
            if (StrUtil.isNotBlank(project.getCaseIndex())) {
                addDocumentNumber(document, project.getCaseIndex());
            } else {
                addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号");
            }
            
            // 8. 添加委托单位名称
            addClientUnit(document, project);
            
            // 9. 添加审计引言段落（从 form_commit=30 获取）
            addAuditIntro(document, contentMap, project);
            
            // 10. 按照前端页面的固定目录结构生成内容（带标题，支持子章节）
            addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project);

            // 二、总体评价（从接口获取 evaluate 字段）
            if (StrUtil.isNotBlank(evaluate)) {
                XWPFParagraph sectionPara2 = document.createParagraph();
                sectionPara2.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun2 = sectionPara2.createRun();
                sectionRun2.setText("二、总体评价");
                sectionRun2.setBold(true);
                sectionRun2.setFontSize(18);
                sectionRun2.setFontFamily("仿宋_GB2312");
                sectionRun2.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，无首行缩进
                addParagraphWithoutIndent(document, evaluate);
            }

            addSectionWithTitle(document, String.format("三、%s主要业绩", project.getPersonName()), null, contentMap, project);

            addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
                Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);

            // 五、审计建议（从接口获取 suggestion 字段）
            if (StrUtil.isNotBlank(suggestion)) {
                XWPFParagraph sectionPara5 = document.createParagraph();
                sectionPara5.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun5 = sectionPara5.createRun();
                sectionRun5.setText("五、审计建议");
                sectionRun5.setBold(true);
                sectionRun5.setFontSize(18);
                sectionRun5.setFontFamily("仿宋_GB2312");
                sectionRun5.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, suggestion);
            }
            
            // 六、其他事项说明（先添加标题）
            XWPFParagraph sectionPara6 = document.createParagraph();
            sectionPara6.setAlignment(ParagraphAlignment.LEFT);
            XWPFRun sectionRun6 = sectionPara6.createRun();
            sectionRun6.setText("六、其他事项说明");
            sectionRun6.setBold(true);
            sectionRun6.setFontSize(18);
            sectionRun6.setFontFamily("仿宋_GB2312");
            sectionRun6.setColor("2c3e50");
            
            // 添加固定内容（两段话）
            addOtherMattersSectionFixedContent(document, project);
            
            // 添加分页符
            addPageBreak(document);
            
            // 11. 添加尾页（签名页）
            addSignaturePage(document);
            
            // 10. 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            String fileName = "经济责任审计报告_" + projectName + ".docx";
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));

            // 11. 输出文档
            try (OutputStream out = response.getOutputStream()) {
                document.write(out);
                out.flush();
            }

        } catch (Exception e) {
            throw new RuntimeException("生成审计报告失败", e);
        } finally {
            ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
        }
    }

    /**
     * 根据项目 ID 和选中的取证单生成并下载审计报告
     */
    @Operation(summary = "根据项目 ID 和选中的取证单生成审计报告")
    @PostMapping("/generateWithEvidences")
    public void generateAuditReportWithEvidences(
            @RequestParam Integer projectId,
            @RequestBody(required = false) Map<String, Object> requestBody,
            HttpServletResponse response) {
        double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();

        try {
            ZipSecureFile.setMinInflateRatio(0.001);

            // 1. 从请求体中获取选中的取证单 ID 列表
            List<Long> evidenceIds = new ArrayList<>();
            if (requestBody != null && requestBody.containsKey("evidenceIds")) {
                List<Integer> evidenceIdInts = (List<Integer>) requestBody.get("evidenceIds");
//                for (Integer id : evidenceIdInts) {
//                    evidenceIds.add(id.longValue());
//                }
                evidenceIds = evidenceIdInts.stream().distinct().map(Long::valueOf).collect(Collectors.toList());
            }
            
            log.info("根据项目 ID 和选中的取证单生成审计报告 - 项目 ID: {}, 取证单 IDs: {}", projectId, evidenceIds);

            // 2. 查询该项目的所有审计数据（包括报告和取证单），并对选中的取证单进行 AI 分析
            PwlProject project = pwlProjectService.getById(projectId);
            Map<String, Object> data = auditReportService.queryAuditDataByProjectIdWithAnalysis(project, evidenceIds);
            List<AuditReport> reports = (List<AuditReport>) data.get("reports");
            String evaluate = (String) data.get("evaluate");  // 审计总体评价
            String suggestion = (String) data.get("suggestion");  // 改进意见和建议
            
            log.info("=== 生成 Word 报告（带勾选）===");
            log.info("项目 ID: {}", projectId);
            log.info("勾选取证单 IDs: {}", evidenceIds);
            log.info("evaluate 字数：{}", evaluate != null ? evaluate.length() : 0);
            log.info("suggestion 字数：{}", suggestion != null ? suggestion.length() : 0);
            
            // 3. 筛选出用户选中的取证单
//            List<AuditEvidence> selectedEvidences = new ArrayList<>();
//            if (!evidenceIds.isEmpty() && allEvidences != null) {
//                for (AuditEvidence evidence : allEvidences) {
//                    if (evidenceIds.contains(evidence.getId())) {
//                        selectedEvidences.add(evidence);
//                        log.info("选中取证单：ID={}, summaryTitle={}", evidence.getId(), evidence.getSummaryTitle());
//                    }
//                }
//            }
//            log.info("共选中 {} 条取证单", selectedEvidences.size());
            
            // 4. 构建内容映射
            Map<Integer, String> contentMap = new HashMap<>();
            for (AuditReport report : reports) {
                if (report.getFormCommit() != null && StrUtil.isNotBlank(report.getReportContent())) {
                    String content = extractTextFromReportContent(report.getReportContent());
//                    log.info("formCommit={}, 提取的内容={}", report.getFormCommit(), content);
                    contentMap.put(report.getFormCommit(), content);
                }
            }
            
            // 5. 获取项目信息
            String projectName = project.getName() != null ? project.getName() : "";
            
            // 6. 使用 XWPFDocument 直接生成 Word 文档（不使用模板）
            XWPFDocument document = new XWPFDocument();
            
            // 7. 添加封面页
            addCoverPage(document, project);
            
            // 添加分页符
            addPageBreak(document);
            
            // 8. 添加正文标题（居中）
            addBodyTitle(document, "经济责任审计报告");
            
            // 9. 添加文号（右对齐，蓝色）- 从 pwl_project.case_index 获取
            if (StrUtil.isNotBlank(project.getCaseIndex())) {
                addDocumentNumber(document, project.getCaseIndex());
            } else {
                addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号");
            }
            
            // 10. 添加委托单位名称
            addClientUnit(document, project);
            
            // 11. 添加审计引言段落（从 form_commit=30 获取）
            addAuditIntro(document, contentMap, project);
            
            // 12. 按照前端页面的固定目录结构生成内容（带标题，支持子章节）
            addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project);

            // 二、总体评价（从接口获取 evaluate 字段）
            if (StrUtil.isNotBlank(evaluate)) {
                XWPFParagraph sectionPara2 = document.createParagraph();
                sectionPara2.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun2 = sectionPara2.createRun();
                sectionRun2.setText("二、总体评价");
                sectionRun2.setBold(true);
                sectionRun2.setFontSize(18);
                sectionRun2.setFontFamily("仿宋_GB2312");
                sectionRun2.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, evaluate);
            }

            // 三、主要业绩（从 contentMap 获取 formCommit=30 的数据）
            if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) {
                XWPFParagraph sectionPara3 = document.createParagraph();
                sectionPara3.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun3 = sectionPara3.createRun();
                sectionRun3.setText(String.format("三、%s主要业绩", project.getPersonName()));
                sectionRun3.setBold(true);
                sectionRun3.setFontSize(18);
                sectionRun3.setFontFamily("仿宋_GB2312");
                sectionRun3.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, contentMap.get(30));
            }

            addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
                Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);

            // 五、审计建议（从接口获取 suggestion 字段）
            if (StrUtil.isNotBlank(suggestion)) {
                XWPFParagraph sectionPara5 = document.createParagraph();
                sectionPara5.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun5 = sectionPara5.createRun();
                sectionRun5.setText("五、审计建议");
                sectionRun5.setBold(true);
                sectionRun5.setFontSize(18);
                sectionRun5.setFontFamily("仿宋_GB2312");
                sectionRun5.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, suggestion);
            }
            
            // 六、其他事项说明（先添加标题）
            XWPFParagraph sectionPara6 = document.createParagraph();
            sectionPara6.setAlignment(ParagraphAlignment.LEFT);
            XWPFRun sectionRun6 = sectionPara6.createRun();
            sectionRun6.setText("六、其他事项说明");
            sectionRun6.setBold(true);
            sectionRun6.setFontSize(18);
            sectionRun6.setFontFamily("仿宋_GB2312");
            sectionRun6.setColor("2c3e50");
            
            // 添加固定内容（两段话）
            addOtherMattersSectionFixedContent(document, project);
            
            // 如果选中了取证单，添加取证单详情部分
//            if (!selectedEvidences.isEmpty()) {
//                addPageBreak(document);
//                addEvidenceSection(document, selectedEvidences);
//            }
            
            // 添加分页符
            addPageBreak(document);
            
            // 13. 添加尾页（签名页）
            addSignaturePage(document);
            
            // 14. 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            String fileName = "经济责任审计报告_" + projectName + ".docx";
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));

            // 15. 输出文档
            try (OutputStream out = response.getOutputStream()) {
                document.write(out);
                out.flush();
            }

        } catch (Exception e) {
            throw new RuntimeException("生成审计报告失败", e);
        } finally {
            ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
        }
    }

    /**
     * 根据项目 ID、选中的取证单和章节内容生成审计报告（使用前端传递的内容，不查数据库）
     */
    @Operation(summary = "根据项目 ID 和章节内容生成审计报告")
    @PostMapping("/generateWithContent")
    public void generateAuditReportWithContent(
            @RequestParam Integer projectId,
            @RequestBody Map<String, Object> requestBody,
            HttpServletResponse response) {
        double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();

        try {
            ZipSecureFile.setMinInflateRatio(0.001);

            // 1. 从请求体中获取选中的取证单 ID 列表
            List<Long> evidenceIds = new ArrayList<>();
            if (requestBody.containsKey("evidenceIds")) {
                List<Integer> evidenceIdInts = (List<Integer>) requestBody.get("evidenceIds");
                evidenceIds = evidenceIdInts.stream().distinct().map(Long::valueOf).collect(Collectors.toList());
            }
            
            // 2. 获取前端传递的章节内容
            List<Map<String, Object>> chapters = (List<Map<String, Object>>) requestBody.get("chapters");
            
            log.info("=== 生成 Word 报告（使用前端传递内容）===");
            log.info("项目 ID: {}", projectId);
            log.info("勾选取证单 IDs: {}", evidenceIds);
            log.info("章节数量：{}", chapters != null ? chapters.size() : 0);
            
            // 3. 构建内容映射（直接使用前端传递的内容）
            Map<Integer, String> contentMap = new HashMap<>();
            if (chapters != null) {
                for (Map<String, Object> chapter : chapters) {
                    Integer formCommit = (Integer) chapter.get("formCommit");
                    String reportContent = (String) chapter.get("reportContent");
                    
                    if (formCommit != null && StrUtil.isNotBlank(reportContent)) {
                        // 直接使用前端传递的内容，不需要从数据库读取
                        contentMap.put(formCommit, reportContent);
                        log.info("加载章节：formCommit={}, content 长度={}", formCommit, reportContent.length());
                    }
                }
            }
            
            // 4. 获取项目信息
            PwlProject project = pwlProjectService.getById(projectId);
            String projectName = project.getName() != null ? project.getName() : "";
            
            // 5. 获取 evaluate 和 suggestion（从 request body 获取）
            String evaluate = requestBody.containsKey("evaluate") ? (String) requestBody.get("evaluate") : "";
            String suggestion = requestBody.containsKey("suggestion") ? (String) requestBody.get("suggestion") : "";
            
            // 6. 使用 XWPFDocument 直接生成 Word 文档（不使用模板）
            XWPFDocument document = new XWPFDocument();
            
            // 7. 添加封面页
            addCoverPage(document, project);
            
            // 添加分页符
            addPageBreak(document);
            
            // 8. 添加正文标题（居中）
            addBodyTitle(document, "经济责任审计报告");
            
            // 9. 添加文号（右对齐，蓝色）
            if (StrUtil.isNotBlank(project.getCaseIndex())) {
                addDocumentNumber(document, project.getCaseIndex());
            } else {
                addDocumentNumber(document, "XX 专字 [202X]4501Z000X 号");
            }
            
            // 10. 添加委托单位名称
            addClientUnit(document, project);
            
            // 11. 添加审计引言段落（从 form_commit=30 获取）
            addAuditIntro(document, contentMap, project);
            
            // 12. 按照前端页面的固定目录结构生成内容（带标题，支持子章节）
            addSectionWithTitle(document, "一、基本情况", Arrays.asList(10, 41, 42, 43), contentMap, project);

            // 二、总体评价
            if (StrUtil.isNotBlank(evaluate)) {
                XWPFParagraph sectionPara2 = document.createParagraph();
                sectionPara2.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun2 = sectionPara2.createRun();
                sectionRun2.setText("二、总体评价");
                sectionRun2.setBold(true);
                sectionRun2.setFontSize(18);
                sectionRun2.setFontFamily("仿宋_GB2312");
                sectionRun2.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, evaluate);
            }

            // 三、主要业绩（从前端传递的 contentMap 获取）
            if (contentMap.containsKey(30) && StrUtil.isNotBlank(contentMap.get(30))) {
                XWPFParagraph sectionPara3 = document.createParagraph();
                sectionPara3.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun3 = sectionPara3.createRun();
                sectionRun3.setText(String.format("三、%s主要业绩", project.getPersonName()));
                sectionRun3.setBold(true);
                sectionRun3.setFontSize(18);
                sectionRun3.setFontFamily("仿宋_GB2312");
                sectionRun3.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, contentMap.get(30));
            }

            addSectionWithTitle(document, "四、履行经济责任的主要情况及审计发现的问题与责任认定",
                Arrays.asList(51, 52, 53, 54, 55, 56, 57), contentMap, project);

            // 五、审计建议
            if (StrUtil.isNotBlank(suggestion)) {
                XWPFParagraph sectionPara5 = document.createParagraph();
                sectionPara5.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun sectionRun5 = sectionPara5.createRun();
                sectionRun5.setText("五、审计建议");
                sectionRun5.setBold(true);
                sectionRun5.setFontSize(18);
                sectionRun5.setFontFamily("仿宋_GB2312");
                sectionRun5.setColor("2c3e50");
                document.createParagraph();

                // 添加内容，首行缩进
                addParagraphWithIndent(document, suggestion);
            }
            
            // 六、其他事项说明（先添加标题）
            XWPFParagraph sectionPara6 = document.createParagraph();
            sectionPara6.setAlignment(ParagraphAlignment.LEFT);
            XWPFRun sectionRun6 = sectionPara6.createRun();
            sectionRun6.setText("六、其他事项说明");
            sectionRun6.setBold(true);
            sectionRun6.setFontSize(18);
            sectionRun6.setFontFamily("仿宋_GB2312");
            sectionRun6.setColor("2c3e50");
            document.createParagraph();
            
            // 添加固定内容（两段话）
            addOtherMattersSectionFixedContent(document, project);
            
            // 添加分页符
            addPageBreak(document);
            
            // 13. 添加尾页（签名页）
            addSignaturePage(document);
            
            // 14. 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            String fileName = "经济责任审计报告_" + projectName + ".docx";
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));

            // 15. 输出文档
            try (OutputStream out = response.getOutputStream()) {
                document.write(out);
                out.flush();
            }

        } catch (Exception e) {
            throw new RuntimeException("生成审计报告失败", e);
        } finally {
            ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
        }
    }
    
    /**
     * 添加封面页
     */
    private void addCoverPage(XWPFDocument document, PwlProject project) {
        // 添加多个空行，使内容垂直居中
        for (int i = 0; i < 8; i++) {
            document.createParagraph();
        }
        
        // 主标题：ABC 有限公司原董事长××同志（黑体，二号，加粗）
        XWPFParagraph titlePara1 = document.createParagraph();
        titlePara1.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun titleRun1 = titlePara1.createRun();
        titleRun1.setText(String.format("%s原%s %s", project.getName(), project.getPosition(), project.getPersonName()));
        titleRun1.setBold(true);
        titleRun1.setFontSize(22); // 二号
        titleRun1.setFontFamily("黑体");
        
        // 副标题：经济责任审计报告（黑体，二号，加粗）
        XWPFParagraph titlePara2 = document.createParagraph();
        titlePara2.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun titleRun2 = titlePara2.createRun();
        titleRun2.setText("经济责任审计报告");
        titleRun2.setBold(true);
        titleRun2.setFontSize(22); // 二号
        titleRun2.setFontFamily("黑体");
        
        // 添加空行
        for (int i = 0; i < 6; i++) {
            document.createParagraph();
        }
        
        // 文号（黑体，小二，加粗）
        String docNumber = (project != null && StrUtil.isNotBlank(project.getCaseIndex())) 
            ? project.getCaseIndex() : "XX 专字 [202X]4501Z000X 号";
        XWPFParagraph docNumberPara = document.createParagraph();
        docNumberPara.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun docNumberRun = docNumberPara.createRun();
        docNumberRun.setText(docNumber);
        docNumberRun.setBold(true);
        docNumberRun.setFontSize(18); // 小二
        docNumberRun.setFontFamily("黑体");
        
        // 添加多个空行到底部
        for (int i = 0; i < 15; i++) {
            document.createParagraph();
        }
        
        // 底部署名：XX 会计师事务所（黑体，小二，加粗）
        XWPFParagraph footerPara = document.createParagraph();
        footerPara.setAlignment(ParagraphAlignment.CENTER);
        XWPFRun footerRun = footerPara.createRun();
        footerRun.setText("XX 会计师事务所");
        footerRun.setBold(true);
        footerRun.setFontSize(18); // 小二
        footerRun.setFontFamily("黑体");
    }
    
    /**
     * 添加分页符
     */
    private void addPageBreak(XWPFDocument document) {
        XWPFParagraph para = document.createParagraph();
        XWPFRun run = para.createRun();
        run.addBreak(BreakType.PAGE);
    }
    
    /**
     * 添加带首行缩进的段落（支持多段文本）
     * @param document Word 文档对象
     * @param text 文本内容（可能包含多个段落，用换行符分隔）
     */
    private void addParagraphWithIndent(XWPFDocument document, String text) {
        if (StrUtil.isBlank(text)) {
            return;
        }
        
        // 按换行符分割成多个段落
        String[] paragraphs = text.split("\n");
        
        for (String paragraph : paragraphs) {
            if (StrUtil.isNotBlank(paragraph.trim())) {
                XWPFParagraph para = document.createParagraph();
                // 设置首行缩进 2 字符（1 字符约等于 240 二十分之一磅）
                para.setFirstLineIndent(480); // 2 字符 = 480 单位
                
                XWPFRun run = para.createRun();
                run.setText(paragraph.trim());
                run.setFontSize(12); // 小四
                run.setFontFamily("仿宋_GB2312");
            }
        }
    }
    
    /**
     * 添加无首行缩进的段落（支持多段文本，用于总体评价和审计建议）
     * @param document Word 文档对象
     * @param text 文本内容（可能包含多个段落，用换行符分隔）
     */
    private void addParagraphWithoutIndent(XWPFDocument document, String text) {
        if (StrUtil.isBlank(text)) {
            return;
        }
        
        // 按换行符分割成多个段落
        String[] paragraphs = text.split("\n");
        
        for (String paragraph : paragraphs) {
            if (StrUtil.isNotBlank(paragraph.trim())) {
                XWPFParagraph para = document.createParagraph();
                // 不设置首行缩进
                
                XWPFRun run = para.createRun();
                run.setText(paragraph.trim());
                run.setFontSize(12); // 小四
                run.setFontFamily("仿宋_GB2312");
            }
        }
    }
    
    /**
     * 添加签名页（尾页）- 从 doc/尾页.docx 读取
     */
    private void addSignaturePage(XWPFDocument document) {
        // 直接使用代码绘制签名页，避免复制模板带来的格式问题
        createSignaturePageWithTable(document);
        log.info("成功添加尾页（使用代码直接绘制）");
    }
    
    /**
     * 使用表格方式创建尾页（备用方案）
     */
    private void createSignaturePageWithTable(XWPFDocument document) {
        // 创建一个 3 行 3 列的表格
        XWPFTable table = document.createTable(3, 3);
        
        // 设置表格样式 - 添加边框
        CTTbl ctTbl = table.getCTTbl();
        CTTblPr tblPr = ctTbl.getTblPr();
        
        // 设置表格宽度为 100%（自适应文档宽度）
        // 表格宽度单位：二十分之一磅，A4 纸默认宽度约 9696（约 485 磅）
        CTTblWidth tblWidth = tblPr.getTblW();
        if (tblWidth == null) {
            tblWidth = tblPr.addNewTblW();
        }
        tblWidth.setW(java.math.BigInteger.valueOf(9696)); // 表格宽度（约 100% 文档宽度）
        tblWidth.setType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth.DXA);
        
        // 设置表格右对齐
        tblPr.addNewJc().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc.RIGHT);
        
        // 设置表格边框 - 保留所有边框，包括内部横线
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblBorders borders = tblPr.addNewTblBorders();
        // 上边框（实线）
        borders.addNewTop().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getTop().setSz(java.math.BigInteger.valueOf(4));
        borders.getTop().setSpace(java.math.BigInteger.valueOf(0));
        // 下边框（实线）
        borders.addNewBottom().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getBottom().setSz(java.math.BigInteger.valueOf(4));
        borders.getBottom().setSpace(java.math.BigInteger.valueOf(0));
        // 左边框（实线）
        borders.addNewLeft().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getLeft().setSz(java.math.BigInteger.valueOf(4));
        borders.getLeft().setSpace(java.math.BigInteger.valueOf(0));
        // 右边框（实线）
        borders.addNewRight().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getRight().setSz(java.math.BigInteger.valueOf(4));
        borders.getRight().setSpace(java.math.BigInteger.valueOf(0));
        // 内部横线（实线）- 第 2 行和第 3 行之间需要横线
        borders.addNewInsideH().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getInsideH().setSz(java.math.BigInteger.valueOf(4));
        borders.getInsideH().setSpace(java.math.BigInteger.valueOf(0));
        // 内部竖线（实线）
        borders.addNewInsideV().setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder.SINGLE);
        borders.getInsideV().setSz(java.math.BigInteger.valueOf(4));
        borders.getInsideV().setSpace(java.math.BigInteger.valueOf(0));
        
        // 设置列宽比例（按照模板文件设置）
        // 第 1 列：XX 会计师事务所 - 约 15% 宽度
        // 第 2 列：中国注册会计师 - 约 25% 宽度  
        // 第 3 列：签名区域 - 约 60% 宽度
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid tblGrid = ctTbl.addNewTblGrid();
        // 第 1 列宽度（约 1500 twips = 75 磅）
        tblGrid.addNewGridCol().setW(java.math.BigInteger.valueOf(1500));
        // 第 2 列宽度（约 2500 twips = 125 磅）
        tblGrid.addNewGridCol().setW(java.math.BigInteger.valueOf(2500));
        // 第 3 列宽度（约 6000 twips = 300 磅）
        tblGrid.addNewGridCol().setW(java.math.BigInteger.valueOf(6000));
        
        // 第一行
        XWPFTableRow row1 = table.getRow(0);
        // 设置第一行高度为 9 行字（12 磅 * 9 = 108 磅，1 磅=1/72 英寸）
        row1.setHeight(7776); // 单位是二十分之一磅，108 磅 * 72 = 7776
        
        // 第一行第一列：XX 会计师事务所（仿宋_GB2312，小四）- 跨两行合并
        XWPFTableCell cell1_1 = row1.getCell(0);
        // 设置垂直合并（向下合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr1_1 = cell1_1.getCTTc().getTcPr();
        if (tcPr1_1 == null) {
            tcPr1_1 = cell1_1.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
        tcPr1_1.setVMerge(vMerge);
        XWPFParagraph para1_1 = cell1_1.getParagraphs().get(0);
        XWPFRun run1_1 = para1_1.createRun();
        run1_1.setText("XX 会计师事务所");
        run1_1.setFontSize(12); // 小四
        run1_1.setFontFamily("仿宋_GB2312");
        para1_1.setIndentationFirstLine(480);
        
        // 第一行第二列：中国注册会计师（仿宋_GB2312，小四）- 跨两行合并
        XWPFTableCell cell1_2 = row1.getCell(1);
        // 设置垂直合并（向下合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr1_2 = cell1_2.getCTTc().getTcPr();
        if (tcPr1_2 == null) {
            tcPr1_2 = cell1_2.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge1_2 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge1_2.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
        tcPr1_2.setVMerge(vMerge1_2);
        
        // 第 2 列内容：两个"中国注册会计师"之间间隔 7 个空行
        // 第一个"中国注册会计师"
        XWPFParagraph para1_2_first = cell1_2.addParagraph();
        XWPFRun run1_2_first = para1_2_first.createRun();
        run1_2_first.setText("中国注册会计师");
        run1_2_first.setFontSize(12); // 小四
        run1_2_first.setFontFamily("仿宋_GB2312");
                
        // 添加 7 个空行（原来是 6 个，现在增加 1 个）
        for (int i = 0; i < 7; i++) {
            XWPFParagraph emptyPara = cell1_2.addParagraph();
            emptyPara.createRun();
        }
                
        // 第二个"中国注册会计师"
        XWPFParagraph para1_2_second = cell1_2.addParagraph();
        XWPFRun run1_2_second = para1_2_second.createRun();
        run1_2_second.setText("中国注册会计师");
        run1_2_second.setFontSize(12); // 小四
        run1_2_second.setFontFamily("仿宋_GB2312");
        
        // 第一行第三列：签名线（仿宋_GB2312，小四）- 跨两行合并 - 先画下划线，再写 XXX
        XWPFTableCell cell1_3 = row1.getCell(2);
        // 设置垂直合并（向下合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr1_3 = cell1_3.getCTTc().getTcPr();
        if (tcPr1_3 == null) {
            tcPr1_3 = cell1_3.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge1_3 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge1_3.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.RESTART);
        tcPr1_3.setVMerge(vMerge1_3);
        
        // 第 3 列内容：
        // 第一段：下划线（空行）
        XWPFParagraph para1_3_line1 = cell1_3.addParagraph();
        XWPFRun run1_3_line1 = para1_3_line1.createRun();
        run1_3_line1.setText("____________________"); // 用下划线字符画线
        run1_3_line1.setFontSize(12); // 小四
        run1_3_line1.setFontFamily("仿宋_GB2312");
        para1_3_line1.setAlignment(ParagraphAlignment.CENTER);
        
        // 第二段：XXX
        XWPFParagraph para1_3_name1 = cell1_3.addParagraph();
        XWPFRun run1_3_name1 = para1_3_name1.createRun();
        run1_3_name1.setText("XXX");
        run1_3_name1.setFontSize(12); // 小四
        run1_3_name1.setFontFamily("仿宋_GB2312");
        para1_3_name1.setAlignment(ParagraphAlignment.CENTER);
        
        // 添加 5 个空行（让第二条下划线比第 2 列的第二个“中国注册会计师”高一行）
        for (int i = 0; i < 5; i++) {
            XWPFParagraph emptyPara = cell1_3.addParagraph();
            emptyPara.createRun();
        }
        
        // 第三段：下划线（空行）
        XWPFParagraph para1_3_line2 = cell1_3.addParagraph();
        XWPFRun run1_3_line2 = para1_3_line2.createRun();
        run1_3_line2.setText("____________________"); // 用下划线字符画线
        run1_3_line2.setFontSize(12); // 小四
        run1_3_line2.setFontFamily("仿宋_GB2312");
        para1_3_line2.setAlignment(ParagraphAlignment.CENTER);
        
        // 第四段：XXX
        XWPFParagraph para1_3_name2 = cell1_3.addParagraph();
        XWPFRun run1_3_name2 = para1_3_name2.createRun();
        run1_3_name2.setText("XXX");
        run1_3_name2.setFontSize(12); // 小四
        run1_3_name2.setFontFamily("仿宋_GB2312");
        para1_3_name2.setAlignment(ParagraphAlignment.CENTER);
        
        // XXX 下方添加 6 个空行（距离底部边框）
        for (int i = 0; i < 6; i++) {
            XWPFParagraph emptyPara = cell1_3.addParagraph();
            emptyPara.createRun();
        }
        
        // 第二行
        XWPFTableRow row2 = table.getRow(1);
        // 不设置固定高度，让行高自适应内容
        
        // 第二行第一列：被合并（不显示内容）
        XWPFTableCell cell2_1 = row2.getCell(0);
        // 设置垂直合并（继续上一行的合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr2_1 = cell2_1.getCTTc().getTcPr();
        if (tcPr2_1 == null) {
            tcPr2_1 = cell2_1.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge2 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge2.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
        tcPr2_1.setVMerge(vMerge2);
        
        // 第二行第二列：被合并（不显示内容）
        XWPFTableCell cell2_2 = row2.getCell(1);
        // 设置垂直合并（继续上一行的合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr2_2 = cell2_2.getCTTc().getTcPr();
        if (tcPr2_2 == null) {
            tcPr2_2 = cell2_2.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge2_2 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge2_2.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
        tcPr2_2.setVMerge(vMerge2_2);
        
        // 第二行第三列：被合并（不显示内容）
        XWPFTableCell cell2_3 = row2.getCell(2);
        // 设置垂直合并（继续上一行的合并）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr2_3 = cell2_3.getCTTc().getTcPr();
        if (tcPr2_3 == null) {
            tcPr2_3 = cell2_3.getCTTc().addNewTcPr();
        }
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge vMerge2_3 = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge.Factory.newInstance();
        vMerge2_3.setVal(org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge.CONTINUE);
        tcPr2_3.setVMerge(vMerge2_3);
        
        // 第三行 - 使用 gridAfter 来正确合并单元格
        XWPFTableRow row3 = table.getRow(2);
        
        // 第三行第一列：中国·南宁（仿宋_GB2312，小四）
        XWPFTableCell cell3_1 = row3.getCell(0);
        XWPFParagraph para3_1 = cell3_1.getParagraphs().get(0);
        XWPFRun run3_1 = para3_1.createRun();
        run3_1.setText("中国·南宁");
        run3_1.setFontSize(12); // 小四
        run3_1.setFontFamily("仿宋_GB2312");
        para3_1.setIndentationFirstLine(480);
        
        // 第三行第二列和第三列合并：二〇二 X 年 X 月 XX 日（仿宋_GB2312，小四）
        XWPFTableCell cell3_2 = row3.getCell(1);
        // 设置合并单元格的属性
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr tcPr = cell3_2.getCTTc().getTcPr();
        if (tcPr == null) {
            tcPr = cell3_2.getCTTc().addNewTcPr();
        }
        // 设置 gridSpan 为 2（合并 2 列，即第 2 列和第 3 列）
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber gridSpan = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDecimalNumber.Factory.newInstance();
        gridSpan.setVal(java.math.BigInteger.valueOf(2));
        tcPr.setGridSpan(gridSpan);
        
        XWPFParagraph para3_2 = cell3_2.getParagraphs().get(0);
        XWPFRun run3_2 = para3_2.createRun();
        run3_2.setText("二〇二 X 年 X 月 XX 日");
        run3_2.setFontSize(12); // 小四
        run3_2.setFontFamily("仿宋_GB2312");
        // 右对齐
        para3_2.setAlignment(ParagraphAlignment.RIGHT);
        
        // 关键：删除第 3 列的 XML 节点，避免边框问题
        // 通过表格的 CTTbl 获取第三行，然后删除第三个单元格
        org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow ctRow3 = ctTbl.getTrList().get(2);
        // 删除第三个单元格 (索引从 0 开始，所以删除索引 2)
        if (ctRow3.getTcList().size() > 2) {
            ctRow3.removeTc(2);
        }
    }
    
    /**
     * 添加正文标题（居中）
     */
    private void addBodyTitle(XWPFDocument document, String title) throws IOException {
        XWPFParagraph titlePara = document.createParagraph();
        titlePara.setAlignment(ParagraphAlignment.CENTER);
        
        // 设置行距为 1.5 倍
        CTSpacing spacing = titlePara.getCTP().addNewPPr().addNewSpacing();
        spacing.setLineRule(org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule.AUTO);
        spacing.setLine(java.math.BigInteger.valueOf(360)); // 1.5 倍行距
        
        XWPFRun titleRun = titlePara.createRun();
        titleRun.setText(title);
        titleRun.setBold(true);
        titleRun.setFontSize(16); // 三号 (16 磅)
        titleRun.setFontFamily("仿宋_GB2312");
        
        // 添加 2 个空行
        document.createParagraph();
        document.createParagraph();
    }

    /**
     * 添加文号（右对齐，蓝色）
     */
    private void addDocumentNumber(XWPFDocument document, String docNumber) throws IOException {
        XWPFParagraph para = document.createParagraph();
        para.setAlignment(ParagraphAlignment.RIGHT);
        
        // 设置行距为 1.5 倍
        CTSpacing spacing = para.getCTP().addNewPPr().addNewSpacing();
        spacing.setLineRule(org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule.AUTO);
        spacing.setLine(java.math.BigInteger.valueOf(360)); // 1.5 倍行距
        
        XWPFRun run = para.createRun();
        run.setText(docNumber);
        run.setFontSize(12); // 小四
        run.setFontFamily("仿宋_GB2312");
        run.setColor("449ED4"); // 蓝色
        
        // 添加 2 个空行
        document.createParagraph();
        document.createParagraph();
    }
    
    /**
     * 添加委托单位名称
     */
    private void addClientUnit(XWPFDocument document, PwlProject project) throws IOException {
        XWPFParagraph para = document.createParagraph();
        para.setAlignment(ParagraphAlignment.LEFT);
        
        // 设置行距为 1.5 倍
        CTSpacing spacing = para.getCTP().addNewPPr().addNewSpacing();
        spacing.setLineRule(org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule.AUTO);
        spacing.setLine(java.math.BigInteger.valueOf(360)); // 1.5 倍行距
        
        XWPFRun run = para.createRun();
        run.setText(project.getName()+":");
        run.setFontSize(12); // 小四
        run.setFontFamily("仿宋_GB2312");
        run.setBold(true);
        run.setColor("449ED4"); // 蓝色
        
        // 添加 1 个空行
        document.createParagraph();
    }
    
    /**
     * 添加审计引言段落
     */
    private void addAuditIntro(XWPFDocument document, Map<Integer, String> contentMap, PwlProject project) throws IOException {
        // 从 form_commit=30 获取审计引言内容
        /*String intro = contentMap.get(30);
            
        if (intro == null || intro.trim().isEmpty()) {
            // 如果数据库中没有数据，使用默认内容
            intro = "我们接受委托，自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日，对 XX 同志自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任公司董事长（具体职务）期间的经济责任履行情况进行了就地审计。公司及 XX 同志的责任是提供与本次审计相关的全部资料，并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下：";
        }*/
        String intro = "我们接受%s委托，自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日，对%s自 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日担任%s %s期间的经济责任履行情况进行了就地审计。%s及%s责任是提供与本次审计相关的全部资料，并保证其真实、完整。我们的责任是在实施审计工作的基础上发表审计意见。审计工作为发表意见提供了合理基础。现将审计情况报告如下：";
            
        // 整段话作为一个段落（仿宋_GB2312，小四）
        XWPFParagraph para = document.createParagraph();
        para.setAlignment(ParagraphAlignment.LEFT);
        
        // 设置行距为 1.5 倍
        CTSpacing spacing = para.getCTP().addNewPPr().addNewSpacing();
        spacing.setLineRule(org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule.AUTO);
        spacing.setLine(java.math.BigInteger.valueOf(360)); // 1.5 倍行距
        
        XWPFRun run = para.createRun();
        run.setText(String.format(intro, project.getName(), project.getPersonName(), project.getName(), project.getPosition(), project.getName(), project.getPersonName()));;
        run.setFontSize(12); // 小四
        run.setFontFamily("仿宋_GB2312");
            
        // 设置首行缩进 2 个中文字符（POI 中使用半字符单位，1 中文字符=2 英文字符=480 单位）
        para.setIndentationFirstLine(480); // 1 个中文字符
            
        // 添加 1个空行
        document.createParagraph();
    }
    /**
     * 添加章节（带标题和前缀，支持子章节）
     */
    private void addSectionWithTitle(XWPFDocument document, String sectionTitle, 
                                     List<Integer> formCommits, Map<Integer, String> contentMap, PwlProject project) {
        log.info("添加章节标题：{}", sectionTitle);
        
        // 一级标题（仿宋_GB2312，三号，加粗）
        XWPFParagraph sectionPara = document.createParagraph();
        sectionPara.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun sectionRun = sectionPara.createRun();
        sectionRun.setText(sectionTitle);
        sectionRun.setBold(true);
        sectionRun.setFontSize(16); // 三号
        sectionRun.setFontFamily("仿宋_GB2312");
        
        // 添加子章节内容（根据固定的子章节结构）
        if (formCommits != null && !formCommits.isEmpty()) {
            for (Integer formCommit : formCommits) {
                // 获取子章节标题（从 AuditReportEnum 中获取）
                String subsectionTitle = getSubsectionTitle(formCommit, project);
                
                // 添加子章节标题（仿宋_GB2312，小四）
                XWPFParagraph subsectionPara = document.createParagraph();
                subsectionPara.setAlignment(ParagraphAlignment.LEFT);
                XWPFRun subsectionRun = subsectionPara.createRun();
                subsectionRun.setText(subsectionTitle);
                subsectionRun.setFontSize(12); // 小四
                subsectionRun.setFontFamily("仿宋_GB2312");
                subsectionPara.setIndentationFirstLine(480);
                
                // 添加子章节内容（如果有）
                String content = contentMap.get(formCommit);
                if (content != null && !content.trim().isEmpty()) {
                    // 按换行符分割内容
                    String[] lines = content.split("\\n");
                    for (String line : lines) {
                        if (!line.trim().isEmpty()) {
                            XWPFParagraph contentPara = document.createParagraph();
                            contentPara.setAlignment(ParagraphAlignment.LEFT);
                            XWPFRun contentRun = contentPara.createRun();
                            contentRun.setText(line.trim());
                            contentRun.setFontSize(12); // 小四
                            contentRun.setFontFamily("仿宋_GB2312");
                            
                            // 设置首行缩进 1 个中文字符
                            contentPara.setIndentationFirstLine(480);
                        }
                    }
                    
                    // 添加空行
                    document.createParagraph();
                }
            }
        }
        
        // 添加空行
        document.createParagraph();
    }
    
    /**
     * 根据 formCommit 获取子章节标题
     */
    private String getSubsectionTitle(Integer formCommit, PwlProject project) {
        return switch (formCommit) {
            case 10 -> "（一）审计依据";
            case 41 -> String.format("（二）%s概况", project.getName());
            case 42 -> String.format("（三）%s任职及分工情况", project.getPersonName());
            case 43 -> "（四）实施审计的基本情况";
            case 51 -> "（一）贯彻执行党和国家经济方针政策、决策部署情况";
            case 52 -> "（二）企业发展战略规划的制定、执行和效果情况";
            case 53 -> "（三）重大经济事项的决策、执行和效果情况";
            case 54 -> "（四）企业法人治理结构的建立、健全和运行情况，内部控制制度的制定和执行情况";
            case 55 -> "（五）企业财务的真实合法效益情况，风险管控情况，境外资产管理情况，生态环境保护情况";
            case 56 -> "（六）在经济活动中落实有关党风廉政建设责任和遵守廉洁从业规定情况";
            case 57 -> "（七）以往审计发现问题的整改情况";
            default -> "";
        };
    }
    
    /**
     * 添加其他事项说明的固定内容（两段话）
     */
    private void addOtherMattersSectionFixedContent(XWPFDocument document, PwlProject project) {
        // （一）本报告依据委托方及 ABC 公司提供的审计相关资料出具，审计意见受所提供资料真实性及完整性的影响。（仿宋_GB2312，小四）
        XWPFParagraph para1 = document.createParagraph();
        para1.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun run1 = para1.createRun();
        run1.setText(String.format("（一）本报告依据委托方及%s提供的审计相关资料出具，审计意见受所提供资料真实性及完整性的影响。", project.getName()));
        run1.setFontSize(12); // 小四
        run1.setFontFamily("仿宋_GB2312");
        para1.setIndentationFirstLine(480);
        
        // （二）本报告仅供委托方 XX 单位了解 XX 同志经济责任履行情况之用，未经本所书面同意不得用作其他用途，因使用不当造成的后果，与执行本次专项审计业务的注册会计师及会计师事务所无关。（仿宋_GB2312，小四）
        XWPFParagraph para2 = document.createParagraph();
        para2.setAlignment(ParagraphAlignment.LEFT);
        XWPFRun run2 = para2.createRun();
        run2.setText(String.format("（二）本报告仅供委托方%s了解%s经济责任履行情况之用，未经本所书面同意不得用作其他用途，因使用不当造成的后果，与执行本次专项审计业务的注册会计师及会计师事务所无关。", project.getName(), project.getPersonName()));
        run2.setFontSize(12); // 小四
        run2.setFontFamily("仿宋_GB2312");
        para2.setIndentationFirstLine(480);
    }
    
    /**
     * 从 reportContent 中提取纯文本内容
     */
    private String extractTextFromReportContent(String reportContent) {
        try {
            JSONObject json = JSONObject.parseObject(reportContent);
            StringBuilder sb = new StringBuilder();
            
            if (json.containsKey("sections")) {
                JSONArray sections = json.getJSONArray("sections");
                for (int i = 0; i < sections.size(); i++) {
                    JSONObject section = sections.getJSONObject(i);
                    
                    // 提取章节内容
                    if (section.containsKey("content") && StrUtil.isNotBlank(section.getString("content"))) {
                        sb.append(section.getString("content")).append("\n");
                    }
                    
                    // 提取小节内容
                    if (section.containsKey("records")) {
                        JSONArray records = section.getJSONArray("records");
                        for (int j = 0; j < records.size(); j++) {
                            JSONObject record = records.getJSONObject(j);
//                            if (record.containsKey("name") && StrUtil.isNotBlank(record.getString("name"))) {
//                                sb.append(record.getString("name")).append("\n"); // 子章节标题
//                            }
                            if (record.containsKey("content") && StrUtil.isNotBlank(record.getString("content"))) {
                                sb.append(record.getString("content")).append("\n");
                            }
                        }
                    }
                }
            }
            
            return sb.toString().trim();
        } catch (Exception e) {
            // JSON 解析失败，直接返回原内容
            return reportContent;
        }
    }

    private void processParagraphs(XWPFDocument document) {
        List<XWPFParagraph> originalParas = new ArrayList<>(document.getParagraphs());

        for (XWPFParagraph para : originalParas) {
            String text = para.getText();
            if (text == null || !text.contains("\n")) {
                continue;
            }

            String[] parts = text.replace("\r\n", "\n").replace("\r", "\n").split("\n");
            
            // 在原段落位置之前插入新段落
            int pos = document.getPosOfParagraph(para);
            
            // 修正：按正序插入（从前往后）
            for (int i = 0; i < parts.length; i++) {
                // 创建新段落
                XWPFParagraph newPara = document.insertNewParagraph(para.getCTP().newCursor());
                
                // 复制样式
                newPara.getCTP().setPPr(para.getCTP().getPPr());
                
                XWPFRun newRun = newPara.createRun();
                newRun.setText(parts[i].trim());
                
                if (!para.getRuns().isEmpty()) {
                    newRun.getCTR().setRPr(para.getRuns().get(0).getCTR().getRPr());
                }
            }
            
            // 删除原段落
            document.removeBodyElement(pos + parts.length);
        }
    }

    /**
     * 保存审计报告到数据库
     */
    @Operation(summary = "保存审计报告")
    @PostMapping("/save")
    public ApiResult<AuditReport> saveAuditReport(@RequestBody AuditReportSaveRequest request) {
        try {
            final User loginUser = getLoginUser();
            
            log.info("接收到审计报告保存请求 - 用户：{}, 项目：{}", loginUser.getUsername(), request.getProjectName());
            return auditReportService.saveAuditReport(request);
        } catch (Exception e) {
            log.error("保存审计报告异常", e);
            return fail("保存审计报告异常：" + e.getMessage(), null);
        }
    }
    
    /**
     * 根据 projectId 和 formCommit 查询审计报告
     */
    @Operation(summary = "查询审计报告")
    @PostMapping("/query")
    public ApiResult<AuditReport> queryAuditReport(
            @RequestParam Integer projectId,
            @RequestParam Integer formCommit) {
        try {
            log.info("查询审计报告 - 项目 ID: {}, formCommit: {}", projectId, formCommit);
            return auditReportService.queryAuditReport(projectId, formCommit);
        } catch (Exception e) {
            log.error("查询审计报告异常", e);
            return fail("查询审计报告异常：" + e.getMessage(), null);
        }
    }
    
    /**
     * AI 生成默认话术（根据章节标题）
     */
    @Operation(summary = "AI 生成默认话术")
    @PostMapping("/generateDefaultText")
    public ApiResult<?> generateDefaultText(@RequestBody AuditReportDefaultTextRequest request) {
        try {
            final User loginUser = getLoginUser();
            Integer formCommit = request.getFormCommit();
            log.info("AI 生成默认话术 - formCommit: {}, chapterTitle: {}", formCommit, request.getChapterTitle());
                
            // 获取章节标题
            String title = request.getChapterTitle();
            if (title == null || title.trim().isEmpty()) {
                title = AuditReportEnum.getByCode(formCommit) != null
                    ? AuditReportEnum.getByCode(formCommit).getDesc()
                    : "审计内容";
            }
                            
            // 从请求体中获取选中的取证单 ID 列表
            List<Integer> evidenceIds = request.getEvidenceIds();
//            List<Long> evidenceIds = new ArrayList<>();
//            if (request.getEvidenceIds() != null && !request.getEvidenceIds().isEmpty()) {
//                evidenceIds = request.getEvidenceIds().stream()
//                    .map(Long::valueOf)
//                    .collect(Collectors.toList());
//            }
            List<AuditEvidence> evidences = auditEvidenceService.listByIds(evidenceIds);
            
            // 构建取证单数据字符串，用于 prompt
            boolean isEvidenceUsed = false;
            StringBuilder evidenceDataBuilder = new StringBuilder();
            if (evidences != null && !evidences.isEmpty()) {
                evidenceDataBuilder.append("\n【取证单数据】\n");
                for (int i = 0; i < evidences.size(); i++) {
                    AuditEvidence evidence = evidences.get(i);
                    evidenceDataBuilder.append(String.format("\n--- 取证单 %d ---\n", i + 1));
                    if (evidence.getSummaryTitle() != null && !evidence.getSummaryTitle().trim().isEmpty()) {
                        evidenceDataBuilder.append("核心问题标题：").append(evidence.getSummaryTitle()).append("\n");
                    }
                    if (evidence.getAuditMatter() != null && !evidence.getAuditMatter().trim().isEmpty()) {
                        evidenceDataBuilder.append("审计事项描述：").append(evidence.getAuditMatter()).append("\n");
                    }
                    if (evidence.getAuditRecord() != null && !evidence.getAuditRecord().trim().isEmpty()) {
                        evidenceDataBuilder.append("审计核查事实：").append(evidence.getAuditRecord()).append("\n");
                    }
                    if (evidence.getAuditFinding() != null && !evidence.getAuditFinding().trim().isEmpty()) {
                        evidenceDataBuilder.append("审计发现问题：").append(evidence.getAuditFinding()).append("\n");
                    }
                    if (evidence.getEvidenceBasis() != null && !evidence.getEvidenceBasis().trim().isEmpty()) {
                        evidenceDataBuilder.append("定性依据：").append(evidence.getEvidenceBasis()).append("\n");
                    }
                    if (evidence.getHandling() != null && !evidence.getHandling().trim().isEmpty()) {
                        evidenceDataBuilder.append("处理措施：").append(evidence.getHandling()).append("\n");
                    }
                    if (evidence.getSuggestion() != null && !evidence.getSuggestion().trim().isEmpty()) {
                        evidenceDataBuilder.append("整改建议：").append(evidence.getSuggestion()).append("\n");
                    }
                }
            }
                
            // 构建提示词，根据 formCommit 使用不同的默认 prompt
            PwlProject project = pwlProjectService.getById(request.getProjectId());
            if (project == null) {
                return fail("项目不存在，projectId: " + request.getProjectId(), null);
            }
            String kbId = project.getKbId();
            String companyName = project.getName();
            String personName = project.getPersonName();
            String prompt = "";
            StringBuilder promptBuilder = new StringBuilder();
            if (formCommit == 20) {
                // 二、总体评价的特殊提示词模板
                // 查询该项目的所有取证单，判断是否有发现问题
//                LambdaQueryWrapper<AuditEvidence> evidenceWrapper = new LambdaQueryWrapper<>();
//                evidenceWrapper.eq(AuditEvidence::getProjectId, request.getProjectId())
//                              .eq(AuditEvidence::getDeleted, 0);
//                List<AuditEvidence> allEvidences = auditEvidenceMapper.selectList(evidenceWrapper);
                
                // 判断是否有问题（检查是否存在 auditFinding 不为空或有问题的记录）
                boolean hasIssues = false;
                if (evidences != null) {
                    hasIssues = evidences.stream()
                        .anyMatch(e -> e.getAuditFinding() != null && !e.getAuditFinding().trim().isEmpty());
                }
                promptBuilder.append(String.format("综合本次经济责任审计情况，我们对%s 任职期间经济责任履行情况的总体评价如下（本部分应基于审计事实，作出客观、公正的概括；既要肯定成绩，也需指出不足，并与第三、%s 主要业绩、四部分履行经济责任的主要情况及审计发现的问题与责任认定相呼应）：\n", personName, personName));
                
                // 如果审计没有问题，添加特定段落
                if (!hasIssues) {
                    promptBuilder.append("\n审计期间未收到涉及").append(personName).append("的举报信息；根据现有审计程序与所获证据，也未发现其存在违反党风廉政及廉洁从业规定的明显问题或行为线索。");
                }

                isEvidenceUsed = true;

            } else if (formCommit == 30) {
                // 三、XX 同志主要业绩的特殊提示词模板
                isEvidenceUsed = true;
                promptBuilder.append(String.format("请根据传入的取证单及相关资料库的数据，总结%s 任职期间的主要业绩。要求：1.基于审计事实和数据；2.突出重要贡献和成绩；3.内容客观真实；4.条理清晰。\n", personName));
            } else if (formCommit == 41) {
                // （二）XX 公司概况的特殊提示词模板
                promptBuilder.append(String.format("请生成关于'%s'的详细说明。要求按以下格式返回：\n" +
                    "%s 为（说明企业性质，如：国有独资/控股公司），成立于 XXXX 年 X 月 X 日，注册资本**元，法定代表人 XX，统一社会信用代码：XXX。\n" +
                    "公司主营业务为……（简述主营业务）。\n" +
                    "%s 下设……（组织架构）。", title, companyName, companyName));
            } else if (formCommit == 42) {
                // （三）XX 同志任职及分工情况的特殊提示词模板
                promptBuilder.append(String.format("请生成关于《%s任职及分工情况》的详细说明。按以下格式返回：\n" +
                    "%s(同本章节标题人名) 自 XXXX 年 X 月至 XXXX 年 X 月任 XXXX（单位名称）XXX（职务名称），其主要职责是：...（描述主要工作职责）。", personName, personName));
            } else if (formCommit == 43) {
                // （四）实施审计的基本情况，要求返回两段内容
                promptBuilder.append(String.format("请生成关于'%s'的详细说明。要求返回两段内容：\n" +
                    "第一段格式：本次审计的时间范围是 20XX 年 XX 月 XX 日至 20XX 年 XX 月 XX 日。本次审计以 %s 会计报表、账簿、凭证及相关经济活动资料为基础，对 %s 任职期间履行经济责任的情况进行审查，主要包括：...（具体内容）。\n" +
                    "第二段格式：%s 及 %s 对所提供的与审计相关的会计资料以及其他证明材料作出了书面承诺，对其真实性和完整性负责。我们按照审计实施方案确定的范围和内容，实施了在当时情况下认为有必要采取的审计程序和方法，包括（具体的程序和方案）等，并对重要事项进行了必要的延伸和追溯。",
                        title, companyName, personName, companyName, personName));
            } else if (formCommit >= 51 && formCommit <= 57) {
                // 生成指令
                promptBuilder.append(String.format("\n请根据上述取证单数据，为%s生成审计报告'%s'章节内容。\n", personName, title));
                promptBuilder.append("要求生成三段：\n");
                promptBuilder.append("1. 履职情况 + 发现问题及责任认定\n");
                promptBuilder.append("2. 原因分析\n");
                promptBuilder.append("3. 责任界定\n");
                promptBuilder.append("\n注意：输出内容中不要显示取证单编号，直接生成正式的审计报告内容。\n");
                isEvidenceUsed = true;
            }

            prompt = promptBuilder.toString();

//            prompt = String.format("请为审计报告章节'%s'生成内容。\n", title) +
//                    "数据源：1)取证单(已通过evidenceIds传入) 2)阿里云资料库(自动检索)\n" +
//                    "要求：优先用取证单事实，结合资料库数据，不足时生成框架内容，勿编造数据。\n";

            if(isEvidenceUsed && !evidenceIds.isEmpty()){
                // 将 ID 列表转换为逗号分隔的字符串
                String evidenceIdsStr = evidenceIds.stream()
                        .map(String::valueOf)
                        .collect(Collectors.joining(","));
                promptBuilder.append(String.format("取证单编号（仅用于查询数据，不要在输出中显示）：%s\n", evidenceIdsStr));
                promptBuilder.append(evidenceDataBuilder);

                prompt += "数据源：1)取证单内容 2)阿里云资料库(自动检索)\n" +
                        "要求：优先用取证单事实，结合资料库数据，勿编造数据。\n" +
                        "最终的生成内容里面不要说明引用资料来源，不要说基于审计取证单及相关资料\n";
            }
            else {
                prompt += "\n要求：数据源必须严格使用阿里云资料库(自动检索)，不能凭空捏造数据。同时返回信息里面，不必说明基于阿里云资料库xxxx。\n";
            }

            log.info("生成审计报告标题：{}，AI 提示词：{}", request.getChapterTitle(), prompt);
            
            // TODO 调用 AI 接口
            String result = invokeAIAnalysis(prompt, kbId, loginUser.getUsername());
                
            // 保存历史记录到数据库
            try {
                // 构建请求数据
                JSONObject requestData = new JSONObject();
                requestData.put("projectId", request.getProjectId());
                requestData.put("formCommit", formCommit);
                requestData.put("chapterTitle", request.getChapterTitle());
                                    
                // 根据 formCommit 生成不同的接口名称
                String interfaceName = "/api/ai/auditReport/generateDefaultText_" + formCommit;
                                    
                // 生成请求哈希
                String requestHash = DigestUtil.md5Hex(interfaceName + ":" + requestData.toJSONString());
                                    
                // 保存历史记录
                aiHistoryService.saveHistory(
                    Long.valueOf(request.getProjectId()),
                    requestHash,
                    interfaceName,
                    requestData.toJSONString(),
                    result,
                    loginUser.getUserId(),
                    loginUser.getUsername(),
                    loginUser.getTenantId()
                );
                log.info("保存 AI 生成历史记录成功 - projectId: {}, interfaceName: {}", request.getProjectId(), interfaceName);
            } catch (Exception e) {
                log.warn("保存 AI 生成历史记录失败", e);
            }
                
            return success(result);
        } catch (Exception e) {
            log.error("AI 生成默认话术异常", e);
            return fail("AI 生成默认话术异常：" + e.getMessage(), null);
        }
    }
    
    /**
     * AI 分析用户自定义输入
     */
    @Operation(summary = "AI 分析用户自定义输入")
    @PostMapping("/analyzeUserInput")
    public ApiResult<?> analyzeUserInput(
            @RequestParam String projectId,
            @RequestParam Integer formCommit,
            @RequestParam String userQuestion,
            @RequestParam(required = false) String chapterContent) {
        try {
            final User loginUser = getLoginUser();
            log.info("AI 分析用户自定义输入 - formCommit: {}, userQuestion: {}", formCommit, userQuestion);
            
            // 获取章节标题
            String chapterTitle = AuditReportEnum.getByCode(formCommit) != null 
                ? AuditReportEnum.getByCode(formCommit).getDesc() 
                : "审计内容";
            
            // 构建提示词
            StringBuilder promptBuilder = new StringBuilder();
            promptBuilder.append(String.format("当前章节：%s\n", chapterTitle));
            if (chapterContent != null && !chapterContent.trim().isEmpty()) {
                promptBuilder.append(String.format("当前章节内容：%s\n\n", chapterContent));
            }
            promptBuilder.append(String.format("用户问题：%s\n\n", userQuestion));
            promptBuilder.append("请根据用户的问题，为该审计报告章节提供专业、详细的分析内容。要求：1.回答要针对用户问题；2.使用审计专业术语；3.内容要有实际价值；4.字数在 300-500 字左右。");

            PwlProject project = pwlProjectService.getById(projectId);
            // TODO 调用 AI 接口
            String result = invokeAIAnalysis(promptBuilder.toString(), project.getKbId(), loginUser.getUsername());
            
            return success(result);
        } catch (Exception e) {
            log.error("AI 分析用户自定义输入异常", e);
            return fail("AI 分析用户自定义输入异常：" + e.getMessage(), null);
        }
    }
    
    /**
     * 调用 AI 生成默认话术
     */
    /*private String invokeDefaultTextGeneration(String prompt, String userName, List<Long> evidenceIds) {
        // 构建请求体
        JSONObject requestBody = new JSONObject();
        JSONObject inputs = new JSONObject();
        inputs.put("query", prompt);
        inputs.put("knowledge", "");
        inputs.put("history", "");
        inputs.put("suggestion", "");
        inputs.put("title", "审计报告默认话术生成");
        
        // 如果有取证单 ID，加入到请求中
        if (evidenceIds != null && !evidenceIds.isEmpty()) {
            inputs.put("evidenceIds", evidenceIds);
            List<AuditEvidence> evidences = auditEvidenceService.listByIds(evidenceIds);
            inputs.put("evidences", evidences);
        }
        
        requestBody.put("inputs", inputs);
        requestBody.put("response_mode", "blocking");
        requestBody.put("user", userName);

        log.info("生成审计报告AI请求参数：{}", JSON.toJSONString(requestBody));
        
        try {
            // 发送 POST 请求
            String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run")
                    .header("Authorization", AUDIT_REPORT_TOKEN)
                    .header("Content-Type", "application/json")
                    .body(requestBody.toString())
                    .timeout(600000)
                    .execute()
                    .body();
            
            log.info("AI 工作流返回结果：{}", result);
            
            // 解析返回的 JSON 字符串
            JSONObject jsonResponse = JSONObject.parseObject(result);
            
            // 检查是否有错误
            if (jsonResponse.containsKey("error")) {
                JSONObject error = jsonResponse.getJSONObject("error");
                String errorMsg = error != null ? error.getString("message") : "未知错误";
                throw new RuntimeException("AI 服务返回错误：" + errorMsg);
            }
            
            JSONObject data = jsonResponse.getJSONObject("data");
            if (data == null) {
                log.error("AI 响应中缺少 data 字段，完整响应：{}", result);
                throw new RuntimeException("AI 服务响应格式异常，缺少 data 字段");
            }
            
            JSONObject outputs = data.getJSONObject("outputs");
            if (outputs == null) {
                log.error("AI 响应中缺少 outputs 字段，完整响应：{}", result);
                throw new RuntimeException("AI 服务响应格式异常，缺少 outputs 字段");
            }
            
            String resultStr = outputs.getString("result");
            
            if (resultStr == null || resultStr.trim().isEmpty()) {
                log.warn("AI 返回的结果为空，完整响应：{}", result);
                return "";
            }
            
            return resultStr;
        } catch (Exception e) {
            log.error("调用 AI 工作流失败", e);
            throw new RuntimeException("调用 AI 工作流失败：" + e.getMessage(), e);
        }
    }*/
    
    /**
     * 调用 AI 分析用户输入
     */
    private String invokeAIAnalysis(String prompt, String kbId, String userName) {
        // 构建请求体
        JSONObject requestBody = new JSONObject();
        JSONObject inputs = new JSONObject();
        if(StrUtil.isNotBlank(kbId)){
            prompt += "\n\n只能在以下知识库中读取数据，标签编号：" + kbId;
        }
        inputs.put("query", prompt);
        inputs.put("title", "审计报告 AI 分析");
        
        requestBody.put("inputs", inputs);
        requestBody.put("response_mode", "blocking");
        requestBody.put("user", userName);
        
        try {
            // 发送 POST 请求
            String result = HttpUtil.createPost("http://1.14.159.185:8180/v1/workflows/run")
                    .header("Authorization", AUDIT_REPORT_TOKEN)
                    .header("Content-Type", "application/json")
                    .body(requestBody.toString())
                    .timeout(600000)
                    .execute()
                    .body();
            
            log.info("AI 工作流返回结果：{}", result);

            // 解析返回的 JSON 字符串
            JSONObject jsonResponse = JSONObject.parseObject(result);

            // 检查是否有错误
            if (jsonResponse.containsKey("error")) {
                JSONObject error = jsonResponse.getJSONObject("error");
                String errorMsg = error != null ? error.getString("message") : "未知错误";
                throw new RuntimeException("AI 服务返回错误：" + errorMsg);
            }

            JSONObject data = jsonResponse.getJSONObject("data");
            if (data == null) {
                log.error("AI 响应中缺少 data 字段，完整响应：{}", result);
                throw new RuntimeException("AI 服务响应格式异常，缺少 data 字段");
            }

            JSONObject outputs = data.getJSONObject("outputs");
            if (outputs == null) {
                log.error("AI 响应中缺少 outputs 字段，完整响应：{}", result);
                throw new RuntimeException("AI 服务响应格式异常，缺少 outputs 字段");
            }

            JSONObject resultJSON = outputs.getJSONObject("result");

            if (resultJSON == null) {
                log.warn("AI 返回的结果为空，完整响应：{}", result);
                return "";
            }

            JSONArray sectionContents = resultJSON.getJSONArray("sectionContent");
            if (sectionContents == null || sectionContents.isEmpty()) {
                log.warn("AI 返回的结果数据为空，完整响应：{}", result);
                return "";
            }

            StringBuilder resultStr = new StringBuilder();
            for(int i = 0; i < sectionContents.size(); i++){
                JSONObject sectionContent = sectionContents.getJSONObject(i);
                String paragraph = sectionContent.getString("paragraph");
                resultStr.append(paragraph);
                if(i < sectionContents.size() - 1){
                    resultStr.append("\n");
                }
            }

            return resultStr.toString();
        } catch (Exception e) {
            log.error("调用 AI 工作流失败", e);
            throw new RuntimeException("调用 AI 工作流失败：" + e.getMessage(), e);
        }
    }
    
    /**
     * 根据取证单生成审计建议
     */
    @Operation(summary = "根据取证单生成审计建议")
    @PostMapping("/generateAuditSuggestion")
    public ApiResult<?> generateAuditSuggestion(
            @RequestParam Integer projectId,
            @RequestBody List<Integer> evidenceIds) {
        try {
            final User loginUser = getLoginUser();
            log.info("根据取证单生成审计建议 - projectId: {}, evidenceIds: {}", projectId, evidenceIds);
            
            if (evidenceIds == null || evidenceIds.isEmpty()) {
                return fail("请提供取证单 ID 列表", null);
            }
            
            // 查询项目信息
            PwlProject project = pwlProjectService.getById(projectId);
            if (project == null) {
                return fail("项目不存在", null);
            }
            
            // 根据取证单 ID 查询所有取证单数据
            List<AuditEvidence> evidences = auditEvidenceMapper.selectBatchIds(evidenceIds);
            if (evidences == null || evidences.isEmpty()) {
                return fail("未找到对应的取证单", null);
            }
            
            // 收集所有取证单的改进或整改建议
            StringBuilder suggestionsBuilder = new StringBuilder();
            suggestionsBuilder.append("以下是各个取证单中的改进或整改建议：\n\n");
            
            int index = 1;
            for (AuditEvidence evidence : evidences) {
                String suggestion = evidence.getSuggestion();
                if (suggestion != null && !suggestion.trim().isEmpty()) {
                    suggestionsBuilder.append(String.format("%d. %s（取证单 ID：%d）\n", index, suggestion, evidence.getId()));
                    index++;
                }
            }
            
            if (index == 1) {
                return fail("所选取证单中没有改进或整改建议内容", null);
            }
            
            // 构建提示词
            String prompt = String.format(
                "请根据以下各个取证单中的改进或整改建议，总结生成一份完整的审计建议。\n\n" +
                "要求：\n" +
                "1.对提供的建议进行分类整理和归纳；\n" +
                "2.使用规范的审计报告语言；\n" +
                "3.建议要具体、可操作；\n" +
                "4.结构清晰，层次分明；\n" +
                "5.字数控制在 800-1500 字。\n\n" +
                "%s\n\n" +
                "请根据以上内容，生成审计建议：",
                suggestionsBuilder
            );
            
            // TODO 调用 AI 接口
            String result = invokeAIAnalysis(prompt, project.getKbId(), loginUser.getUsername());
            
            return success(result);
        } catch (Exception e) {
            log.error("根据取证单生成审计建议异常", e);
            return fail("根据取证单生成审计建议异常：" + e.getMessage(), null);
        }
    }

}
