package com.ruoyi.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.tool.xml.ElementList;
import com.itextpdf.tool.xml.XMLWorker;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.itextpdf.tool.xml.css.CssFile;
import com.itextpdf.tool.xml.css.StyleAttrCSSResolver;
import com.itextpdf.tool.xml.html.CssAppliers;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Tags;
import com.itextpdf.tool.xml.parser.XMLParser;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;
import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;
import com.itextpdf.tool.xml.pipeline.end.ElementHandlerPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.PdfBaseWriter;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.domain.ModelTestTask;
import com.ruoyi.domain.Sample;
import com.ruoyi.domain.TestRecords;
import com.ruoyi.domain.vo.*;
import com.ruoyi.mapper.SampleManagementMapper;
import com.ruoyi.mapper.TaskMapper;
import com.ruoyi.mapper.TestRecordsMapper;
import com.ruoyi.service.PdfTemplateManagementService;
import com.ruoyi.service.ReviewEnterpriseArchiveService;
import com.ruoyi.system.mapper.SysDictDataMapper;
import io.minio.MinioClient;
import io.minio.ObjectWriteArgs;
import io.minio.PutObjectArgs;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * PDF模板管理Service业务层处理
 * @author gxk
 */
@Service
@Transactional
public class PdfTemplateManagementServiceImpl implements PdfTemplateManagementService {

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.url}")
    private String minioEndpoint;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Autowired
    private ReviewEnterpriseArchiveService reviewEnterpriseArchiveService;

    @Autowired
    private TaskMapper taskMapper;

    @Autowired
    private SysDictDataMapper dictDataMapper;

    @Autowired
    private SampleManagementMapper sampleManagementMapper;

    @Autowired
    private TestRecordsMapper testRecordsMapper;

    /**
     * 检验报告PDF下载
     *
     * @param taskId
     * @return
     */
    @SneakyThrows
    @Override
    public String generateInspectionReport(Long taskId) {
        PdfReader reader = null;
        PdfStamper ps = null;
        ByteArrayOutputStream bos = null;
        // 模板绝对路径--服务器
        String fileName = "/template/receipt_template_04_02.pdf";
        // 读取现有模板内容
        reader = new PdfReader(fileName);
        // 创建输出流
        bos = new ByteArrayOutputStream();
        // 实例化PdfStamper准备编辑pdf内容
        ps = new PdfStamper(reader, bos);
        // 获取表单所有元素
        AcroFields fields = ps.getAcroFields();
        // 设置具体字体格式的编码, 不设置的话中文可能不会显示
        BaseFont bf = BaseFont.createFont("/fonts/STSong.TTF", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        fields.addSubstitutionFont(bf);
        // 获取真实的数据
        InspectionReportPdfVO inspectionReportPdfVO = requireData(taskId);
        // 渲染数据
        renderData(fields, inspectionReportPdfVO, ps);
        // 必须要调用这个,否则文档不会生成的
        ps.setFormFlattening(true);
        // 关闭
        ps.close();
        bos.close();
        reader.close();

        // 把第一个模板填入数据后生成一个oss
        String templateName = uploadMinio(bos, "整车信息安全检验临时模板");
        // 创建一个空PDF, 融合两个PDF
        Document document = new Document();
        ByteArrayOutputStream allOut = new ByteArrayOutputStream();
        PdfCopy copy = new PdfCopy(document, allOut);
        document.open();
        // 第一个PDF
        PdfReader templateReader = new PdfReader(minioUrl() + templateName);
        int numberPage = templateReader.getNumberOfPages();
        for (int i = 1;i < numberPage;i++) {
            PdfImportedPage importedPage = copy.getImportedPage(templateReader, i);
            copy.addPage(importedPage);
        }
        // 第二个PDF
        String codeName = getInspectionReportCode(inspectionReportPdfVO);
        PdfReader codeReader = new PdfReader(minioUrl() + codeName);
        for (int i = 1; i <= codeReader.getNumberOfPages(); i++) {
            copy.addPage(copy.getImportedPage(codeReader, i));
        }
        document.close();

        return uploadMinio(allOut, "整车信息安全检验报告-" + getReportName());
    }

    /**
     * 检验报告 - 代码渲染部分
     * @param data
     * @return
     */
    @SneakyThrows
    private String getInspectionReportCode(InspectionReportPdfVO data) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfBaseWriter document = new PdfBaseWriter(outputStream);
        document.open();
        // 代码渲染
        document.setParagraph("报告编号:" + data.getReportNumber(), 10.5f, Element.ALIGN_RIGHT, 0,10f, 0f);
        document.setParagraph("检  验  报  告", 22f, Element.ALIGN_CENTER, 0,0f, 0f);
        PdfPTable signTable = new PdfPTable(new float[]{65, 20});
        signTable.setSpacingBefore(5);
        signTable.setWidthPercentage(85);
        document.writeBottomCell("共 10 页", Element.ALIGN_RIGHT, 1, 1, signTable).setRightIndent(30f);
        document.writeBottomCell("第 10 页", Element.ALIGN_RIGHT, 1, 1, signTable);
        document.addContent(signTable);
        document.setParagraph("7.附录", 10.5f, Element.ALIGN_LEFT, 45,1f, 10f);
        // 添加内容到新页面
        addSamplePhoto(document, data.getSamplePhotos());
        document.setParagraph("图 1 样品照片", 10.5f, Element.ALIGN_CENTER, 0,20f, 0f);
        document.setParagraph("图 2 试验照片", 10.5f, Element.ALIGN_CENTER, 0,20f, 0f);
        // 关闭PDF
        document.close();
        return uploadMinio(outputStream, "整车信息安全检验临时代码");
    }

    /**
     * minio对象存储查看图片地址
     * @return
     */
    private String minioUrl() {
        return minioEndpoint + "/" + bucketName + "/";
    }

    private String getReportName() {
        return DateUtils.parseDateToStr("yyyyMMddHHmm", new Date());
    }

    /**
     * 企业留档文件PDF下载
     * @param taskId
     * @return
     * @throws Exception
     */
    @Override
    public String generateRetentionFile(Long taskId) throws Exception {

        // 活数据查询
        EnterpriseFilePdfVO data = getEnterpriseFileData(taskId);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        // 新建
        PdfBaseWriter document = new PdfBaseWriter(outputStream);
        document.setMargins(40, 40, 50,40);
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        MyHeaderFooter headerFooter = new MyHeaderFooter();
        headerText = data.getInspectionCenterName();
        writer.setPageEvent(headerFooter);
        // 打开
        document.open();
        // 内容
        // 封面
        document.setParagraph(" ", 30f, Element.ALIGN_CENTER, 0,60f, 40f);
        document.setParagraph("企业留档文件", 30f, Element.ALIGN_CENTER, 0,60f, 180f);
        PdfPTable customTable = new PdfPTable(new float[]{15, 50});
        customTable.setWidthPercentage(70);
        document.writeNoBorderCell("检验依据:", Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeBottomCell(data.getInspectionBasis(), Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeNoBorderCell("检验项目:", Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeBottomCell(data.getInspectionItem(), Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeNoBorderCell("创建时间:", Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeBottomCell(data.getCreateTime(), Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeNoBorderCell("委托单位名称:", Element.ALIGN_LEFT, 1, 1, customTable);
        document.writeBottomCell(data.getClientName(), Element.ALIGN_LEFT, 1, 1, customTable);
        document.addContent(customTable);
        document.setParagraph(data.getInspectionCenterName(), 20f, Element.ALIGN_CENTER, 0, 100f, 0f);
        // 下一页
        document.newPage();
        // 创建表格
        List<String> list = Arrays.asList("文件名称", "文件目录", "标准章节", "标准要求", "审查要点");
        float[] columnWidths = {30, 25, 25, 60, 60};
        PdfPTable table = document.createWithHeaderTable(list, columnWidths);
        // 查询数据
        List<ReviewEnterpriseArchiveViewVO> viewList = reviewEnterpriseArchiveService.view(taskId);
        for (ReviewEnterpriseArchiveViewVO view : viewList) {
            if (view.getItems().size() > 0) {
                document.writeCell(view.getFileName(), Element.ALIGN_LEFT, 1, view.getItems().size(), table);
                view.getItems().forEach(item -> {
                    document.writeCell(item.getCatalogue(), Element.ALIGN_LEFT, 1, 1, table);
                    document.writeCell(item.getChapter(), Element.ALIGN_LEFT, 1, 1, table);
                    document.writeCell(item.getStandard(), Element.ALIGN_LEFT, 1, 1, table);
                    document.writeCell(item.getKeyPoint(), Element.ALIGN_LEFT, 1, 1, table);
                });
            }
        }
        document.addContent(table);
        // 最后结尾签字
        PdfPTable signTable = new PdfPTable(new float[]{40, 15});
        signTable.setWidthPercentage(100);
        signTable.setSpacingBefore(40);
        document.writeNoBorderCell("车企负责人签字:", Element.ALIGN_RIGHT, 1, 1, signTable);
        document.writeBottomCell("", Element.ALIGN_RIGHT, 1, 1, signTable);
        document.addContent(signTable);
        // 关闭
        document.close();

        // 上传到Minio
        return uploadMinio(outputStream, "企业留档文件-" + getReportName());
    }

    /**
     * 原始记录PDF下载
     * @param taskId
     * @return
     * @throws Exception
     */
    @Override
    public String generateOriginalRecord(Long taskId) throws Exception{

        // 样品数据
        Sample sample = sampleManagementMapper.selectSampleByTaskId(taskId);
        // 测试用例
        List<TestRecords> recordsList = testRecordsMapper.selectListByGeneralTaskId(taskId);
        // 查询本任务对应的所有样品编号、样品照片
        InspectionReportPdfVO reportData = taskMapper.selectInspectionReportData(taskId);
        // 查询检验负责人
        InspectionPersonnelVO inspectionPersonnel = taskMapper.selectInspectionPersonnelById(taskId);

        // 创建临时文档
        String templateName = temporaryDocument(sample, recordsList, reportData, inspectionPersonnel);
        PdfReader pdfReader = new PdfReader(minioUrl() + templateName);
        totalNum = pdfReader.getNumberOfPages() - 1;
        // 真正文档
        String currentName = temporaryDocument(sample, recordsList, reportData, inspectionPersonnel);
        // copy - 分割
        Document currentDoc = new Document();
        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
        PdfCopy copy = new PdfCopy(currentDoc, arrayOutputStream);
        currentDoc.open();
        PdfReader templateReader = new PdfReader(minioUrl() + currentName);
        int numberOfPages = templateReader.getNumberOfPages();
        for (int i = 2; i <= numberOfPages; i++) {
            PdfImportedPage page = copy.getImportedPage(templateReader, i);
            copy.addPage(page);
        }
        currentDoc.close();
        templateReader.close();
        copy.close();
        return uploadMinio(arrayOutputStream, "车型审查原始记录-" + getReportName());
    }

    /**
     * 创建临时文档
     * @param sample 样品信息
     * @param recordsList 定时任务接收数据List
     * @param reportData
     * @param inspectionPersonnel 检验人员信息
     * @return
     * @throws Exception
     */
    @SneakyThrows
    private String temporaryDocument(Sample sample, List<TestRecords> recordsList, InspectionReportPdfVO reportData, InspectionPersonnelVO inspectionPersonnel) throws Exception {


        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfBaseWriter document = new PdfBaseWriter(outputStream);
        document.setMargins(50, 50, 85, 40);
        PdfWriter writer = PdfWriter.getInstance(document, outputStream);
        OriginalRecordHeaderFooter headerFooter = new OriginalRecordHeaderFooter();
        writer.setPageEvent(headerFooter);
        // open
        document.open();
        document.setParagraph(" ", 10f, Element.ALIGN_LEFT, 0,2f, 10f);
        document.newPage();
        // 内容
        // 1.检验依据
        inspectionBasisSection(document);
        // 2.样品情况
        sampleConditionSection(document, sample);
        // 3.仪器设备信息
        instrumentInformationSection(document);
        // 第一页下方信息
        bottomOfTheFirstPage(document, writer,reportData, inspectionPersonnel);
        // 4.检验结果(循环遍历用例)
        testResultSection(document, recordsList, reportData, inspectionPersonnel);
        // 5.附录
        document.setParagraph("5.附录", 10f, Element.ALIGN_LEFT, 0,2f, 0f);
        // 样品照片
        addSamplePhoto(document, reportData.getSamplePhotos());
        document.setParagraph("图1 样品照片", 10f, Element.ALIGN_CENTER, 0,20f, 0f);
        document.setParagraph("图2 试验照片", 10f, Element.ALIGN_CENTER, 0,20f, 0f);
        // close
        document.close();
        writer.close();
        return uploadMinio(outputStream, "原始记录临时");
    }

    /**
     * 往PDF中添加图片组
     * @param document
     * @param samplePhoto
     */
    @SneakyThrows
    private void addSamplePhoto(PdfBaseWriter document, String samplePhoto) {
        PdfPTable pictureTable = new PdfPTable(new float[]{25, 25, 25, 25});
        pictureTable.setWidthPercentage(90);
        pictureTable.setSpacingBefore(10);
        List<String> pictureList = Arrays.stream(samplePhoto.split(",")).filter(StringUtils::isNotBlank).collect(Collectors.toList());
        int result = pictureList.size();
        while (result % 4 != 0) {
            result++;
        }
        for (int i = 0; i < result; i++) {
            PdfPCell cell = new PdfPCell();
            cell.setBorder(Rectangle.NO_BORDER);
            Optional<String> optionalElement = i < pictureList.size()
                    ? Optional.of(pictureList.get(i))
                    : Optional.empty();
            if (optionalElement.isPresent() && StrUtil.isNotEmpty(optionalElement.get())) {
                Image image = Image.getInstance(new URL((minioEndpoint + optionalElement.get()).replace(" ", "%20")));
                image.scaleAbsolute(100, 100);
                cell.addElement(image);
            } else {
                Paragraph paragraph = new Paragraph();
                cell.addElement(paragraph);
            }
            pictureTable.addCell(cell);
        }
        document.addContent(pictureTable);
    }

    /**
     * 车型试验原始记录 - 1.检验依据
     * @param document
     */
    private void inspectionBasisSection(PdfBaseWriter document) {
        PdfPTable reportNumberTable = new PdfPTable(new float[]{30, 15});
        reportNumberTable.setWidthPercentage(100);
        reportNumberTable.setSpacingBefore(40);
        document.writeNoBorderCell("报告编号:", Element.ALIGN_RIGHT, 1, 1, reportNumberTable);
        document.writeBottomCell(DateUtils.dateTimeNow(), Element.ALIGN_CENTER, 1, 1, reportNumberTable);
        document.addContent(reportNumberTable);
        document.setParagraph("1.检验依据", 10f, Element.ALIGN_LEFT, 0,2f, 10f);
        document.setParagraph("口《汽车整车信息安全技术要求》", 10f, Element.ALIGN_LEFT, 14,0f, 0f);
    }

    /**
     * 车型试验原始记录 - 2.样品情况
     * @param document
     * @param sample
     */
    @SneakyThrows
    private void sampleConditionSection(PdfBaseWriter document, Sample sample) {
        document.setParagraph("2.样品情况", 10f, Element.ALIGN_LEFT, 0,10f, 10f);
        PdfPTable sampleConditionTable = new PdfPTable(new float[]{20, 30, 20, 30});
        sampleConditionTable.setWidthPercentage(100);
        document.writeCell("样品名称", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell(sample.getSampleName(), Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("生产企业", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell(sample.getManufacturingEnterprise(), Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("样品型号", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("xxxxxxx", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("样品编号", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell(sample.getSampleNumber(), Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("样品数量", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell(sample.getNumberOfSamples(), Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        document.writeCell("商标", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        if (ObjectUtil.isNotEmpty(sample.getTrademark())) {
            document.newPdfPCellOfImage(1, 1, Image.getInstance(minioEndpoint + sample.getTrademark()), 30, 30, sampleConditionTable);
        } else {
            document.writeCell("---", Element.ALIGN_CENTER, 1, 1, sampleConditionTable);
        }
        document.writeCell("备注:" + sample.getRemark(), Element.ALIGN_LEFT, 4, 1, sampleConditionTable);
        document.addContent(sampleConditionTable);
    }

    /**
     * 车型试验原始记录 - 3.仪器设备信息
     * @param document
     */
    @SneakyThrows
    private void instrumentInformationSection(PdfBaseWriter document) {
        document.setParagraph("3.仪器设备信息", 10f, Element.ALIGN_LEFT, 0,10f, 0f);
        List<String> equipmentList = Arrays.asList("设备编号", "设备名称", "溯源有效期");
        float[] columnWidths = {20, 20, 60};
        PdfPTable equipmentTable = document.createWithHeaderTable(equipmentList, columnWidths);
        document.writeCell("xxxx", Element.ALIGN_CENTER, 1, 1, equipmentTable);
        document.writeCell("xxxxxxxxx", Element.ALIGN_CENTER, 1, 1, equipmentTable);
        document.writeCell("xxxx-xx-xx", Element.ALIGN_CENTER, 1, 1, equipmentTable);
        document.add(equipmentTable);
    }

    /**
     * 车型试验原始记录 - 第一页下方固定内容
     * @param document
     * @param writer
     * @param reportData
     * @param inspectionPersonnel
     */
    private void bottomOfTheFirstPage(PdfBaseWriter document, PdfWriter writer,InspectionReportPdfVO reportData, InspectionPersonnelVO inspectionPersonnel) {
        PdfPTable inspectorTable = new PdfPTable(new float[]{12, 28, 10, 40});
        inspectorTable.setWidthPercentage(100);
        inspectorTable.setTotalWidth(PageSize.A4.getWidth() - 80);
        document.writeNoBorderCell("检验人员:", Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeNoBorderCell(inspectionPersonnel.getInspectionPersonnel(), Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeNoBorderCell("检验日期:", Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeNoBorderCell(inspectionPersonnel.getInspectionDate(), Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeBottomCell("检验负责人:", Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeBottomCell(inspectionPersonnel.getPrincipal(), Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeBottomCell("检验地点:", Element.ALIGN_LEFT, 1, 1, inspectorTable);
        document.writeBottomCell("口" + reportData.getInspectionCenterAddress() + "  口其他:", Element.ALIGN_LEFT, 1, 1, inspectorTable);
        inspectorTable.writeSelectedRows(0, -1, document.left(), document.bottom() + 80, writer.getDirectContent());
        // 开启下一页
        document.newPage();
    }

    /**
     * 车型试验原始记录 - 4.检验结果
     * @param document
     * @param recordsList
     * @param reportData
     * @param inspectionPersonnel
     */
    private void testResultSection(PdfBaseWriter document, List<TestRecords> recordsList, InspectionReportPdfVO reportData, InspectionPersonnelVO inspectionPersonnel) {
        document.setParagraph("4.检验结果", 10f, Element.ALIGN_LEFT, 0,10f, 0f);
        // 获取数据 - 渲染
        if (CollUtil.isNotEmpty(recordsList)) {
            IntStream.range(0, recordsList.size())
                    .forEach(index -> {
                        TestRecords record = recordsList.get(index);
                        document.setParagraph("4." + (index + 1) + " " + record.getUsecase(), 10f, Element.ALIGN_LEFT, 0,2f, 10f);
                        PdfPTable useCaseTable = new PdfPTable(new float[]{15, 35, 15, 35});
                        useCaseTable.setWidthPercentage(100);
                        useCaseTable.setSplitLate(false);
                        document.writeCell("测试编号", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(record.getUsecaseNo(), Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell("测试时间", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(String.valueOf(record.getTestTime()), Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell("样品编号", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(reportData.getSampleNumberSummary(), Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell("测试人员", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(inspectionPersonnel.getInspectionPersonnel(), Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell("简述", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(record.getDescription(), Element.ALIGN_LEFT, 3, 1, useCaseTable);
                        document.writeCell("风险等级", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(record.getRiskLevel(), Element.ALIGN_LEFT, 3, 1, useCaseTable);
                        document.writeCell("测试方法", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(record.getTestMethod(), Element.ALIGN_LEFT, 3, 1, useCaseTable);
                        document.writeCell("测试结果", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        document.writeCell(record.getTestResult(), Element.ALIGN_LEFT, 3, 1, useCaseTable);
                        document.writeCell("测试详情", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                        PdfPCell cell = new PdfPCell();
                        cell.setColspan(3);
                        cell.setRowspan(1);
                        cell.setHorizontalAlignment(Element.ALIGN_LEFT);
                        // h5渲染到cell
                        Paragraph context = new Paragraph();
                        ElementList elementList = null;
                        try {
                            elementList = parseToElementList(record.getTestDetails(), null);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                        context.addAll(elementList);
                        cell.addElement(context);
                        useCaseTable.addCell(cell);
                        if (ObjectUtil.isNotEmpty(record.getRemediation())) {
                            document.writeCell("修复建议", Element.ALIGN_LEFT, 1, 1, useCaseTable);
                            document.writeCell(record.getRemediation(), Element.ALIGN_LEFT, 3, 1, useCaseTable);
                        }
                        try {
                            document.add(useCaseTable);
                        } catch (DocumentException e) {
                            throw new RuntimeException(e);
                        }
                        document.setParagraph(" ", 10f, Element.ALIGN_LEFT, 0,2f, 10f);
                    });
        }
    }

    /**
     * html转pdf 写法
     * @param html
     * @param css
     * @return
     * @throws IOException
     */
    public static ElementList parseToElementList(String html, String css) throws IOException {
        // CSS
        CSSResolver cssResolver = new StyleAttrCSSResolver();
        if (css != null) {
            CssFile cssFile = XMLWorkerHelper.getCSS(new ByteArrayInputStream(css.getBytes()));
            cssResolver.addCss(cssFile);
        }

        // HTML
        MyFontsProvider fontProvider = new MyFontsProvider();
        CssAppliers cssAppliers = new CssAppliersImpl(fontProvider);
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(cssAppliers);
        htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
        htmlContext.autoBookmark(false);

        // Pipelines
        ElementList elements = new ElementList();
        ElementHandlerPipeline end = new ElementHandlerPipeline(elements, null);
        HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, end);
        CssResolverPipeline cssPipeline = new CssResolverPipeline(cssResolver, htmlPipeline);

        // XML Worker
        XMLWorker worker = new XMLWorker(cssPipeline, true);
        XMLParser p = new XMLParser(worker);
        html = html.replace("<br>", "").replace("<hr>", "").replace("<img>", "").replace("<param>", "").replace("<link>", "");
        p.parse(new ByteArrayInputStream(html.getBytes()));
        return elements;
    }

    static class MyFontsProvider extends XMLWorkerFontProvider {
        public MyFontsProvider() {
            super(null, null);
        }

        @Override
        public Font getFont(final String fontname, String encoding, float size, final int style) {
            return getFontInf();
        }
    }

    /**
     * 设置字体信息
     * @return
     */
    @SneakyThrows
    private static Font getFontInf() {
        // 字体路径
        String fontPath =  "/fonts/STSong.TTF";
        BaseFont baseFont = null;
        Font font = null;
        // 设置字体路径,字体编码,是否将字体嵌入pdf(默认false)
        baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        // 设置默认字体数据
        font = new Font(baseFont, 12f, Font.NORMAL,BaseColor.BLACK);
        return font;
    }

    private static int totalNum = 0;

    public static class OriginalRecordHeaderFooter extends PdfPageEventHelper {


//        PdfTemplate totalPage;

//        @Override
//        public void onOpenDocument (PdfWriter writer, Document document) {
//            PdfContentByte contentByte = writer.getDirectContent();
//            totalPage = contentByte.createTemplate(30, 16);
//        }

        @Override
        @SneakyThrows
        public void onCloseDocument(PdfWriter writer, Document document) {
//            BaseFont font = BaseFont.createFont("/fonts/STSong.TTF", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
//            String text = " 共 " + (writer.getPageNumber()) + " 页";
//            ColumnText.showTextAligned(totalPage, Element.ALIGN_CENTER, new Paragraph(String.valueOf(text), new Font(font, 8f)), 2, 4 ,0);
            totalNum = writer.getPageNumber() - 1;
            System.out.println(totalNum);
        }

        /**
         * 一页加载完成触发,写入页眉和页脚
         */
        @Override
        @SneakyThrows
        public void onEndPage(PdfWriter writer, Document document) {
            BaseFont font = BaseFont.createFont("/fonts/STSong.TTF", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            PdfPTable table = new PdfPTable(2);
            table.setTotalWidth(PageSize.A4.getWidth() - 80);
            table.setWidths(new int[] { 60, 40 });
            table.setLockedWidth(true);
            table.getDefaultCell().setFixedHeight(30);
            table.getDefaultCell().setBorder(Rectangle.BOX);
            table.getDefaultCell().setBorderWidth(0.5f);
            table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
            table.getDefaultCell().setRowspan(1);
            table.getDefaultCell().setColspan(2);
            table.addCell(new Paragraph("中汽研软件测评有限公司", new Font(font)));
            table.getDefaultCell().setRowspan(1);
            table.getDefaultCell().setColspan(1);
            table.addCell(new Paragraph("检验记录", new Font(font)));
            table.getDefaultCell().setRowspan(1);
            table.getDefaultCell().setColspan(1);
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
            table.getDefaultCell().setBorder(Rectangle.BOTTOM + Rectangle.TOP + Rectangle.LEFT + Rectangle.RIGHT);
            table.addCell(new Paragraph("第 " + (writer.getCurrentPageNumber() - 1) + " 页 / 共 "+ totalNum +" 页", new Font(font)));
            // 总页数
//            table.getDefaultCell().setBorder(Rectangle.BOX);
//            table.getDefaultCell().setColspan(1);
//            table.getDefaultCell().setRowspan(1);
//            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
//            table.getDefaultCell().setBorder(Rectangle.BOTTOM + Rectangle.TOP + Rectangle.RIGHT);
//            table.addCell(Image.getInstance(totalPage));
            // 将页眉写到document中,位置可以指定,指定到下面就是页脚
            table.writeSelectedRows(0, -1, 40, PageSize.A4.getHeight() - 20, writer.getDirectContent());
            // 页脚
            PdfPTable bottomTable = new PdfPTable(1);
            bottomTable.setTotalWidth(PageSize.A4.getWidth() - 80);
            bottomTable.setWidths(new int[] { 50});
            bottomTable.setLockedWidth(true);
            bottomTable.getDefaultCell().setFixedHeight(-10);
            bottomTable.getDefaultCell().setBorder(Rectangle.NO_BORDER);
            bottomTable.getDefaultCell().setBorderWidth(0.5f);
            bottomTable.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
            bottomTable.addCell(new Paragraph(String.valueOf(writer.getPageNumber() - 1), new Font(font)));
            bottomTable.writeSelectedRows(0, -1, 40, 40, writer.getDirectContent());
        }


    }

    private static String headerText;

    public static class MyHeaderFooter extends PdfPageEventHelper {

        // 一页加载完成触发,写入页眉和页脚
        @Override
        public void onEndPage(PdfWriter writer, Document document) {
            PdfPTable table = new PdfPTable(1);
            try {
                table.setTotalWidth(PageSize.A4.getWidth() - 80);
                table.setWidths(new int[] { 40 });
                table.setLockedWidth(true);
                table.getDefaultCell().setFixedHeight(-10);
                table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
                table.getDefaultCell().setBorderWidth(0.5f);
                BaseFont font = BaseFont.createFont("/fonts/STSong.TTF", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
                table.addCell(new Paragraph(headerText, new Font(font)));// 可以直接使用addCell(str),不过不能指定字体,中文无法显示
                // 将页眉写到document中,位置可以指定,指定到下面就是页脚
                table.writeSelectedRows(0, -1, 40, PageSize.A4.getHeight() - 20, writer.getDirectContent());
            } catch (Exception de) {
                throw new ExceptionConverter(de);
            }
        }
    }

    /**
     * 获取企业留档文件数据
     * @param taskId
     * @return
     */
    private EnterpriseFilePdfVO getEnterpriseFileData(Long taskId) {
        EnterpriseFilePdfVO enterpriseFileData = taskMapper.selectEnterpriseFileData(taskId);
        enterpriseFileData.setInspectionItem(getInspectionItemByTaskItem(enterpriseFileData.getInspectionItem()));
        return enterpriseFileData;
    }

    /**
     * 获取当前项目的检验项目
     * @return
     */
    private String getInspectionItemByTaskItem(String inspectionItem) {
        List<SysDictData> dictDataList = dictDataMapper.selectDictDataByType("inspection_item");
        List<String> list = Arrays.asList(inspectionItem.split(","));
        StringBuilder taskItem = new StringBuilder();
        for (String item : list) {
            Optional<SysDictData> first = dictDataList.stream().filter(data -> data.getDictValue().equals(item)).findFirst();
            if (first.isPresent()) {
                SysDictData dictData = first.get();
                taskItem.append(dictData.getDictLabel()).append("、");
            }
        }
        return taskItem.substring(0, taskItem.length() - 1);
    }

    /**
     * 上传到minio
     * @param outputStream 输出流
     * @param fileName 文件名
     * @return minio存储地址
     */
    private String uploadMinio(ByteArrayOutputStream outputStream, String fileName) throws Exception {
        // 1.连接Minio
        MinioClient minioClient =MinioClient.builder()
                .endpoint(minioEndpoint)
                .credentials(accessKey, secretKey)
                .build();
        // 2.上传
        // 把os流转为is流
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        String newFileName = assemblyPdfName(fileName);
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(newFileName)
                .contentType("application/pdf")
                .stream(inputStream, inputStream.available(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
        minioClient.putObject(putObjectArgs);
        return newFileName;
    }

    /**
     * 拼接pdf文件名
     * @param name
     * @return
     */
    public String assemblyPdfName(String name) {
        return StringUtils.format("{}/{}.pdf", DateUtils.datePath(), name);
    }

    /**
     * 模拟数据
     * @return
     */
    private InspectionReportPdfVO requireData(Long taskId) {
        InspectionReportPdfVO inspectionReport = taskMapper.selectInspectionReportData(taskId);
        inspectionReport.setInspectionTimeAndLocation("  检验于"
                        + inspectionReport.getTaskBeginTime() + "至"
                        + inspectionReport.getTaskEndTime() + "在"
                        + inspectionReport.getInspectionCenterName() + "进行");
        String taskItem = getInspectionItemByTaskItem(inspectionReport.getInspectionItem());
        inspectionReport.setInspectionItem(taskItem);
        // 检验结论
        inspectionReport.setTestConclusion(getTestConclusion(taskItem, taskId, inspectionReport));
        return inspectionReport;
    }

    /**
     * 获取检验结论
     * @return
     */
    private String getTestConclusion(String taskItem, Long taskId, InspectionReportPdfVO inspectionReport) {
        int num = 0;
        String result = "";
        String issueDate = "";
        String passItemName = "";
        String failItemName = "";
        List<String> taskItemList = Arrays.asList(taskItem.split(","));
        List<InspectionItemVO> list = new ArrayList<>();
        TestConclusionVO testConclusionVO = taskMapper.selectTestConclusion(taskId);
        if (ObjectUtil.isNotEmpty(testConclusionVO.getSystemReviewTaskResult())) {
            list.add(InspectionItemVO.builder()
                            .inspectionItemName(taskItemList.get(num))
                            .taskResult(testConclusionVO.getSystemReviewTaskResult()).build());
            issueDate = testConclusionVO.getSystemReviewTaskDate();
            num++;
        }
        if (ObjectUtil.isNotEmpty(testConclusionVO.getCarReviewTaskResult())) {
            list.add(InspectionItemVO.builder()
                    .inspectionItemName(taskItemList.get(num))
                    .taskResult(testConclusionVO.getCarReviewTaskResult()).build());
            issueDate = testConclusionVO.getCarReviewTaskDate();
            num++;
        }
        if (ObjectUtil.isNotEmpty(testConclusionVO.getModelTestTaskResult())) {
            list.add(InspectionItemVO.builder()
                    .inspectionItemName(taskItemList.get(num))
                    .taskResult(testConclusionVO.getModelTestTaskResult()).build());
            issueDate = testConclusionVO.getModelTestTaskDate();
        }
        inspectionReport.setIssueDate(issueDate);
        for (InspectionItemVO item : list) {
            if (item.getTaskResult().equals(ModelTestTask.TASK_STATUS_PASS)) {
                passItemName += item.getInspectionItemName() + "、";
            }
            if (item.getTaskResult().equals(ModelTestTask.TASK_STATUS_REJECT)) {
                failItemName += item.getInspectionItemName() + "、";
            }
        }
        if (StrUtil.isNotEmpty(passItemName)) {
            passItemName = passItemName.substring(0, passItemName.length() - 1);
            result = "经检验,该样品的"
                    + passItemName
                    + "检验项目的检验结果符合"
                    + inspectionReport.getInspectionBasis()
                    + "的要求,";
        }
        if (StrUtil.isNotEmpty(failItemName)) {
            failItemName = failItemName.substring(0, failItemName.length() - 1);
            result += "该样品的"
                      + failItemName
                      + "检验项目的检验结果不符合"
                      + inspectionReport.getInspectionBasis()
                      + "的要求。";
        }
        if (result.endsWith(",")) {
            result = result.substring(0, result.length() - 1) + "。";
        }
        return "    " + result;
    }

    /**
     * 渲染数据
     * @param fields
     */
    private void renderData(AcroFields fields, InspectionReportPdfVO inspectionReportPdfVO, PdfStamper ps) throws Exception {
        // 取出当前对象所有属性,并赋值到模板里
        // 文本域
        Class<? extends InspectionReportPdfVO> aClass = inspectionReportPdfVO.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            String name = field.getName();
            String key = name.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
            field.setAccessible(true);
            Object o = field.get(inspectionReportPdfVO);
            if (o != null) {
                String value = o.toString();
                fields.setField(key, value);
            }
        }
        // 图片域
        // 样品
//        String url = inspectionReportPdfVO.getBrandImage();
//        if (StrUtil.isNotBlank(url)) {
//            if (url.startsWith(",")) {
//                url = inspectionReportPdfVO.getBrandImage().substring(1);
//            }
//            addImageToPdf("brand", fields, ps, "http://49.232.167.247:22038" + url.split(",")[0]);
//        }
    }


    /**
     * 填充模板中的数据
     * @param fields
     * @param data 是一个Map<String,String> 主要存储 key 表单模板中的单元格名 value为想要赋的值,遍历
     * @param ps
     */
    public void fillData(AcroFields fields, Map<String, String> data, PdfStamper ps) {
        try {
            for (String key : data.keySet()) {
                String value = data.get(key);
                if (key.contains("image")) {
                    addImageToPdf(key, fields, ps, value);
                    continue;
                }
                // 为字段赋值,注意字段名称是区分大小写的
                fields.setField(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 添加图片
     * @param key 应为模板名
     * @param form 动态字段
     * @param stamper
     * @param filePath 本地图片路径
     * @throws DocumentException
     * @throws IOException
     * @throws IOException
     */
    private static void addImageToPdf(String key,AcroFields form, PdfStamper stamper, String filePath) throws DocumentException, IOException, IOException {

        // 通过图片域名获取所在页和坐标,左下角为起点
        int pageNo = form.getFieldPositions(key).get(0).page;
        Rectangle signRect = form.getFieldPositions(key).get(0).position;
        float x = signRect.getLeft();
        float y = signRect.getBottom();

        // 读图片
        Image image = Image.getInstance(filePath);
        // 获取操作的页面
        PdfContentByte under = stamper.getOverContent(pageNo);
        // 根据域的大小缩放图片
        image.scaleToFit(signRect.getWidth(), signRect.getHeight());
        // 添加图片并设置位置(个人通过此设置使得图片垂直水平居中,可参考,具体情况已实际为准)
        image.setAbsolutePosition(x, y);
        under.addImage(image);
    }

    /**
     * 通过属性名获取属性在对象里的值
     * @param obj
     * @param propertyName
     * @return
     * @param <T>
     */
    public static <T> T getPropertyValue(Object obj, String propertyName) {
        try {
            // 获取对象的类
            Class<?> objClass = obj.getClass();

            // 获取类的指定属性
            Field field = objClass.getDeclaredField(propertyName);

            // 设置属性为可访问
            field.setAccessible(true);

            // 获取属性的值
            @SuppressWarnings("unchecked")
            T value = (T) field.get(obj);

            return value;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}