const fs = require('fs')
const path = require('path')
const compressing = require('compressing')
const rimrif = require('rimraf')
const shell = require('shelljs');

const crypto = require('crypto');

// 文档模板生成
const PizZip = require("pizzip");
const Docxtemplater = require("docxtemplater");

// 文档转换
import { Chromiumly } from "chromiumly";
// Chromiumly.configure({ endpoint: "http://8.140.98.43/docs" });
Chromiumly.configure({ endpoint: "http://123.57.204.89/docs" });

import { PDFEngines } from "chromiumly";
const { LibreOffice } = require("chromiumly");
// const { PDFEngines } = require("chromiumly");
const tempDir = path.join(__dirname , "temp");
if(!fs.existsSync(tempDir)){fs.mkdirSync(tempDir)};

const OSS = require("ali-oss");
const ALI_OSS_BUCKET = process.env.ALI_OSS_BUCKET || "hep-textbook"
const ALI_OSS_ACCESS_KEY_ID = process.env.ALI_OSS_ACCESS_KEY_ID || "LTAI5t6AbTiAvXmeoVdJZhL3"
const ALI_OSS_ACCESS_KEY_SECRET = process.env.ALI_OSS_ACCESS_KEY_SECRET || "KLtQRdIW69KLP7jnzHNUf7eKmdptxH"

const bwipjs = require("bwip-js")
export async function toBarCode(text){
    return new Promise(resolve=>{
        bwipjs.toBuffer({
            bcid:"code128",
            text:text,
            scale:1.5,
            height:3,
            includetext:false,
            textalign:"center"
        },(err,png)=>{
            if(err){
                console.error(err)
                resolve(null)
            }else{
                resolve(png)
            }
        })
    })
}

export async function uploadFileToOSS(filePath){
    let client = new OSS({
        // yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
        region: "oss-cn-beijing",
        accessKeyId: ALI_OSS_ACCESS_KEY_ID,
        accessKeySecret: ALI_OSS_ACCESS_KEY_SECRET,
        // 填写Bucket名称。
        bucket: ALI_OSS_BUCKET || "hep-textbook",
    });

    let now = new Date();
    let fileName = getFileName(filePath);
    let fileKey = `export/report/${fileName}`;
    const r1 = await client?.put(fileKey, filePath);
    console.log('put success: %j', r1);
    return r1
}
export function getFileName(filePath) {
    // 使用 '/' 或 '\' 作为分隔符,分割路径
    const parts = filePath.split(/[/\\]/);
    // 返回最后一个部分,即文件名
    return parts.pop();
}
module.exports.uploadFileToOSS = uploadFileToOSS

/**
 * 将给定的文件路径数组打包成指定名称的zip压缩包
 * @param {Array<string>} filePathList - 要打包的文件路径数组
 * @param {string} outputZipName - 输出的zip文件名称
 */
 export function createZip(filePathList, outputZipName,options) {
    let zipStream = new compressing.zip.Stream();
    return new Promise((resolve)=>{
        try {
            let outputPath = path.join(options?.tempDir||tempDir,outputZipName)
            // 遍历文件路径列表,将每个文件添加到zip流中
            for (const filePath of filePathList) {
                // 检查文件是否存在
                if (fs.existsSync(filePath)) {
                    // 将文件添加到zip流中
                    zipStream.addEntry(filePath);
                } else {
                    console.error(`文件不存在: ${filePath}`);
                }
            }
            
            // 创建一个写入流
            const output = fs.createWriteStream(outputPath);
            
            // 使用 compressing 库的 zip 方法将文件打包
            // console.log(filePathList)
            // await compressing.zip.compressDir(filePathList, output);
            // 将zip流写入文件
            zipStream.pipe(output);

            output.on('finish', () => {
                // console.log(`成功创建压缩包: ${outputPath}`);
                resolve(outputPath)
            });

            output.on('error', (error) => {
                console.error('写入压缩包时出错:', error);
                resolve(null)
            });

            // console.log(`成功创建压缩包: ${outputPath}`);
            // return outputPath
        } catch (error) {
            console.error('创建压缩包时出错:', error);
            return null
        }
    })

}
module.exports.createZip = createZip


const download = require('download')
async function downloadUrl(url,options) {
    // console.log(url)
    if(url?.startsWith("/")) {return url};
    let md5 = crypto.createHash('md5');
    let extname = path.extname(url)?.toLocaleLowerCase();
    let filename = md5.update(url).digest('hex') + extname;
    let filepath = path.join(options?.tempDir||tempDir,filename)
    // console.log(filename,filepath)
    try{
        // if(fs.existsSync(filepath)){fs.rmSync(filepath)} // 存在则删除
        if(fs.existsSync(filepath)){return filepath} // 存在则直接返回(md5相同)
        fs.writeFileSync(filepath, await download(url));
        return filepath
    }catch(err){
        console.error(err)
        return null
    }
}

/**
 * 将 DOCX 文件转换为 PDF
 * 
 * @param {string} docxPath - 要转换的 DOCX 文件的路径
 * @param {string} outputPath - 输出 PDF 文件的路径
 * @returns {Promise<void>}
 */
 export async function docxToPdf(docxPath, outputPath,options) {
    let mergeFiles = options?.mergeFiles || []
    let merge = false;
    let mergeFileMap = {};
    if(mergeFiles?.length){
        let plist = []
        for (let index = 0; index < mergeFiles.length; index++) {
            let filePath
            plist.push((async ()=>{
                try{
                    filePath = await downloadUrl(mergeFiles[index],options);
                }catch(err){}
                if(filePath){
                    mergeFileMap[index] = filePath // 按原有顺序整理
                    // filePathList.push(filePath)
                }
                return
            })())
        }
        await Promise.all(plist);
        merge = true;
    }
    let filePathList = mergeFiles?.map((item,index)=>mergeFileMap[index]).filter(item=>item)

    // console.log("DOWNLOADED:",filePathList)
    filePathList = filePathList.map((filepath,index)=>{
        // 按顺序修改文件前缀数字为字母表顺序
        let fileDir = path.dirname(filepath);
        let abc = String.fromCharCode(96+(index+1)); // 字母顺序不会出现 把 1 10 11 12 放在一起的情况
        let num = index+110; // 数字顺序从百位开始,避免首数字排序错乱

        let md5 = crypto.createHash('md5');
        let outmd5 = md5.update(path.basename(filepath)).digest('hex');
        let fileName = num + "_" + outmd5 + path.extname(filepath);

        let orderPath = path.join(fileDir,fileName)
        fs.cpSync(filepath,orderPath);
        fs.readFileSync(filepath);
        return orderPath
    })
    try {
        
        let files = []

        if(docxPath){
            let docxBuffer = fs.readFileSync(docxPath);
            files.push({ data: docxBuffer, ext: "docx" })
        }
        files = [...files,...filePathList]

        let convertOpts = {
            files,
            properties: {
                // 设置页面属性,例如纸张大小和方向
                pageSize: 'A4',
                // orientation: 'portrait',
                margin: {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                }
            },
            pdfa: false, // 根据需要设置
            pdfUA: false, // 根据需要设置
            merge: merge, // 如果只转换一个文件,设置为false
            // metadata: {
            //     // 你可以在这里添加元数据
            // },
            // losslessImageCompression: false,
            // reduceImageResolution: false,
            // quality: 90, // JPG 导出质量
            // maxImageResolution: 300 // 最大图像分辨率
        }
        // console.log("convertOpts",convertOpts)

        let pdfPath,pdfBuffer
        // 方式1:逐个合并
        // let pdfBuffer
        // for (let index = 1; index < files.length; index++) {
        //     let file = files[index];
        //     if(pdfBuffer){
        //         convertOpts.files = [{data:pdfBuffer,ext:"pdf"},file]
        //     }else{
        //         convertOpts.files = [file]
        //     }
        //     pdfBuffer = await LibreOffice.convert(convertOpts);
        // }

        let mainPdfPath = docxPath
        if(docxPath){
            convertOpts.files = [files[0]];
            console.log(convertOpts)
            let mainPdfBuffer = await LibreOffice.convert(convertOpts);
            let md5 = crypto.createHash('md5');
            let outmd5 = md5.update(path.basename(docxPath)).digest('hex');
            mainPdfPath = path.dirname(docxPath)+"/109_"+outmd5+".pdf"
            fs.writeFileSync(mainPdfPath,mainPdfBuffer)
        }

        // 方式2:先合并pdf,后合并docx
        if(files?.length>4){
            let pdfList = [mainPdfPath,...files.slice(1)];
            pdfList = pdfList.filter(item=>item)
            let mergedFileList = await mergePdfListReduce(pdfList,convertOpts) 
            pdfPath = mergedFileList[0];
        // convertOpts.files = [files[0],...mergedFileList]
            // console.log(convertOpts)
            // pdfBuffer = await LibreOffice.convert(convertOpts);
        }else{
            pdfBuffer = await LibreOffice.convert(convertOpts);
        }
        
        // 方式3:全部合并
        // let pdfBuffer = await LibreOffice.convert(convertOpts);

        if(pdfPath){
            fs.cpSync(pdfPath,outputPath);
        }
        // 将 Buffer 写入输出文件
        if(pdfBuffer){
            fs.writeFileSync(outputPath, pdfBuffer);
            console.log(`成功输出 ${outputPath}`);
        }
        return outputPath
    } catch (error) {
        console.error('转换失败:', error);
        return null
    }
}
module.exports.docxToPdf = docxToPdf

const ImageModule = require("@slosarek/docxtemplater-image-module-free");
const sizeOf = require("image-size");

/**
 * 每三个pdf合并一次,直到合并为一个pdf为止
 * @param {} pdfList 
 * @param {*} convertOpts 
 * @returns 
 */
export async function mergePdfListReduce(pdfList,convertOpts){
    console.log("pdfList",pdfList)
    // 所有非PDF转PDF
    for (let index = 0; index < pdfList.length; index++) {
        let file = pdfList[index];
        if(typeof file == "string" && file?.toLocaleLowerCase()?.indexOf("pdf")==-1){
            convertOpts.files = [file];
            let pdfBuffer = await LibreOffice.convert(convertOpts);
            fs.writeFileSync(file+".pdf",pdfBuffer)
            pdfList[index] = file+".pdf"
        }
    }

    let mergeList = []
    let plist = []
    let length = pdfList.length
    for (let index = 0; index < length; index++) {
        let file = pdfList.shift();
        // console.log(file,index,length)
        if(!file) break;
        let files = [file,pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            // pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            // pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            // pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),
            // ,pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift()
            // ,pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift()
            // ,pdfList.shift(),pdfList.shift(),pdfList.shift()
            // ,pdfList.shift(),pdfList.shift(),pdfList.shift()
            // ,pdfList.shift(),pdfList.shift(),pdfList.shift(),pdfList.shift()
        ]; // 每次合并四个
        files=files?.filter(item=>item);
        // console.log(files)
        plist.push(new Promise(async resolve=>{
            if(files?.length==1){ // 单文件直接加载 自动获取后缀
                let onefile = files[0]
                // if(!onefile?.ext){
                //     let extname = path.extname(files[0]).slice(1)?.toLocaleLowerCase();
                //     onefile = {data:fs.readFileSync(onefile),ext:extname}
                // }
                resolve(onefile);
            }else{ // 多文件合并
                convertOpts = {}
                convertOpts.files = files;
                // console.log("多文件合并",convertOpts)
                // pdfEngine合并
                if(false){
                    let mergeBuffer = await PDFEngines.merge(convertOpts)
                    let mergeFilePath = files[0]+".merge.pdf"
                    fs.writeFileSync(mergeFilePath,mergeBuffer)
                    resolve(mergeFilePath)
                }
                // pdfunite合并
                if(true){
                    let mergeFilePath = files[0]+".merge.pdf"
                    pdfUnite(files,mergeFilePath)
                    resolve(mergeFilePath)
                }

            }
        }))
    }
    if(plist?.length){
        mergeList = await Promise.all(plist);
    }
    // console.log("mergeList",mergeList)
    if(mergeList?.length<=1){
        return mergeList;
    }else{
        // console.log("mergePdfListReduce continue:",mergeList)
        return await mergePdfListReduce(mergeList,convertOpts)
    }
}

function pdfUnite(pdfList,outputPath){
    let params = ["pdfunite",...pdfList,outputPath].join(" ")
    try{
        shell.exec(params)
    }catch(err){}
    if(fs.existsSync(outputPath)){
        return outputPath
    }else{
        throw "error: pdfunit merge error"
    }
}

export function renderDocx(inputDocxPath, outputDocxName, data,options){

    let imageOptions = {
        getImage(tagValue,tagName) {
            if(!fs.existsSync(tagValue)){
                throw new Error(`Image not found: ${tagValue}`);
            }
            return fs.readFileSync(tagValue);
        },
        getSize(img) {
            const sizeObj = sizeOf(img);
            console.log(sizeObj);
            return [sizeObj.width, sizeObj.height];
        },
    };
    
    let outputDocxPath = path.join(options?.tempDir||tempDir,outputDocxName)
    // Load the docx file as binary content
    let content = fs.readFileSync(
        inputDocxPath,
        "binary"
    );

    // Unzip the content of the file
    let zip = new PizZip(content);
    let doc = new Docxtemplater(zip, {
        paragraphLoop: true,
        linebreaks: true,
        modules: [new ImageModule(imageOptions)],
    });

    // Render the document (Replace {first_name} by John, {last_name} by Doe, ...)
    Object.keys(data).forEach(key=>{ // 除去空值
        if(data[key]==undefined){
            data[key] = ""
        }
    })
    doc.render(data);

    // Get the zip document and generate it as a nodebuffer
    let buf = doc.getZip().generate({
        type: "nodebuffer",
        // compression: DEFLATE adds a compression step.
        // For a 50MB output document, expect 500ms additional CPU time
        compression: "DEFLATE",
    });

    // buf is a nodejs Buffer, you can either write it to a
    // file or res.send it with express for example.
    fs.writeFileSync(outputDocxPath, buf);
    return outputDocxPath
}
/**
 * docx 替换模板字符串内容
 * @example
    // 要替换内容的模板
    let inputDocx = 'cs.docx'
    // 替换完成的docx文件
    let outputDocx = 'dd.docx'
    // {{xx}} 处要替换的内容
    let replaceData = {
        name: '替换name处的内容',
        age: '替换age处的内容',
    }
    replaceDocx(inputDocx, outputDocx, replaceData)
 */
export function replaceDocx(inputDocxPath, outputDocxPath, options,eventMap) {
    return new Promise((resolve,reject)=>{

    // 解压出来的临时目录
    let md5 = crypto.createHash('md5');
    let outmd5 = md5.update(outputDocxPath).digest('hex')
    let tempDocxPath =  path.join(options?.tempDir||tempDir , outmd5)
    // 要替换的xml文件位置
    let tempDocxXMLName = path.join(tempDocxPath,`word/document.xml`)

    // 压缩文件夹为文件
    let dir_to_docx = (inputFilePath, outputFilePath) => {
        outputFilePath = path.join(options?.tempDir||tempDir,outputFilePath)
        // 创建压缩流
        let zipStream = new compressing.zip.Stream()
        // 写出流
        let outStream = fs.createWriteStream(outputFilePath)
        fs.readdir(inputFilePath, null, (err, files) => {
            if (!err) {
                files.map(file => path.join(inputFilePath, file))
                    .forEach(file => {
                        zipStream.addEntry(file)
                    })
            }
        })
        // 写入文件内容
        zipStream.pipe(outStream)
            .on('close', () => {
                // 打包完成后删除临时目录
                // console.log(tempDocxPath)
                eventMap["onDocxComplete"]&&eventMap["onDocxComplete"](outputFilePath)
                shell.rm("-r",tempDocxPath)
                // rimrif.rimrafSync(tempDocxPath)
                resolve(true)
            })
    }

    // 替换word/document.xml文件中{{xx}}处的内容
    let replaceXML = (data, text) => {
        Object.keys(data).forEach(key => {
            text = text.replaceAll(`{{${key}}}`, data[key])
        })
        return text
    }

    // 解压docx文件替换内容重新打包成docx文件
    compressing.zip.uncompress(inputDocxPath, tempDocxPath)
        .then(() => {
            // 读写要替换内容的xml文件
            fs.readFile(tempDocxXMLName, null, (err, data) => {
                if (!err) {
                    let text = data.toString()
                    text = replaceXML(options, text)
                    fs.writeFile(tempDocxXMLName, text, (err) => {
                        if (!err) {
                            dir_to_docx(tempDocxPath, outputDocxPath)
                        } else {
                            reject(err)
                        }
                    })
                } else {
                    reject(err)
                }
            })
        }).catch(err => {
            reject(err)
        })
    })

}

module.exports.replaceDocx = replaceDocx
function generateObjectId(inputString) {
    inputString = inputString || ""
    inputString = String(inputString)
    const hash = crypto.createHash('sha256').update(inputString).digest('hex');
    const objectId = hash;
    return objectId;
}