package com.hotwj.platform.reportStatistics.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.hotwj.platform.common.utils.PdfWatermarkUtil;
import com.hotwj.platform.reportStatistics.integration.GotenbergClient;
import com.hotwj.platform.reportStatistics.service.impl.DynamicVelocityTemplateLoader;
import com.hotwj.platform.reportStatistics.template.ReportVelocityTool;
import com.hotwj.platform.resourceManagement.company.domain.vo.SysCompanyVo;
import com.hotwj.platform.resourceManagement.company.service.ISysCompanyService;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.dromara.common.core.domain.dto.OssDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.OssService;
import org.dromara.common.core.utils.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class ReportRenderService implements InitializingBean {

    private final GotenbergClient gotenbergClient;
    private final OssService ossService;
    private final ISysCompanyService sysCompanyService;

    @Value("${spring.application.name}")
    private String appName;
    @Value("${report.render.parallel-threshold:4}")
    private int renderParallelThreshold;
    @Value("${report.render.max-concurrency:4}")
    private int renderMaxConcurrency;

    private VelocityEngine velocityEngine;
    private ExecutorService renderExecutor;
    /**
     * 公司公章缓存，避免一键打印时重复下载同一公章
     */
    private final Map<Long, byte[]> companySealCache = new ConcurrentHashMap<>();

    @Override
    public void afterPropertiesSet() {
        initVelocityEngine();
        initRenderExecutor();
    }

    private void initVelocityEngine() {
        Properties p = new Properties();
        // 设置资源加载器：db 优先，classpath 为后备
        p.setProperty(RuntimeConstants.RESOURCE_LOADER, "db,classpath");

        // 配置 DB Loader
        p.setProperty("resource.loader.db.class", DynamicVelocityTemplateLoader.class.getName());
        p.setProperty("resource.loader.db.cache", "false"); // 开发阶段关闭缓存
        p.setProperty("resource.loader.db.modification_check_interval", "0");

        // 配置 Classpath Loader
        p.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName());

        // 编码
        p.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8");
        p.setProperty("output.encoding", "UTF-8");

        velocityEngine = new VelocityEngine(p);
        velocityEngine.init();
        log.info("ReportRenderService：Velocity引擎已通过数据库和类路径加载器完成初始化");
    }

    private void initRenderExecutor() {
        int poolSize = Math.max(1, renderMaxConcurrency);
        ThreadFactory threadFactory = new ThreadFactory() {
            private final java.util.concurrent.atomic.AtomicInteger idx = new java.util.concurrent.atomic.AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("report-render-" + idx.getAndIncrement());
                t.setDaemon(true);
                return t;
            }
        };
        this.renderExecutor = Executors.newFixedThreadPool(poolSize, threadFactory);
        log.info("ReportRenderService：渲染线程池初始化完成，poolSize={}", poolSize);
    }

    @PreDestroy
    public void destroy() {
        if (renderExecutor != null) {
            renderExecutor.shutdown();
        }
    }

    /**
     * 渲染报表
     *
     * @param provider 数据提供者
     * @param params   请求参数
     * @return 渲染后的 HTML 字符串
     */
    public String render(IReportDataProvider provider, Map<String, Object> params) {
        // 1. 准备数据
        Map<String, Object> data = provider.prepareData(params);
        if (data == null) {
            data = MapUtil.newHashMap();
        }
        VelocityContext context = new VelocityContext(data);
        // 注入工具类
        context.put("tool", new ReportVelocityTool());

        // 2. 获取主模版
        String templateCode = provider.getTemplateCode();
        String content = renderTemplate(templateCode, context);

        // 3. 处理布局
        String layoutCode = provider.getLayoutTemplateCode(params);
        if (StrUtil.isNotBlank(layoutCode)) {
            // 将主内容放入 layout 上下文
            context.put("content", content);
            context.put("title", provider.getReportTitle(params));
            return renderTemplate(layoutCode, context);
        }

        return content;
    }

    /**
     * 创建一个新的打印任务构建器
     */
    public ReportPrintJob createJob(java.util.function.Function<String, IReportDataProvider> providerFinder) {
        return new ReportPrintJob(providerFinder);
    }

    /**
     * 创建一个新的打印任务构建器 (不带 Provider 查找功能，无法使用 addCover)
     */
    public ReportPrintJob createJob() {
        return new ReportPrintJob();
    }

    /**
     * 执行打印任务
     * <p>
     * 自动处理混合布局（横向/纵向）的合并逻辑。
     * </p>
     */
    public byte[] renderJob(ReportPrintJob job) {
        if (job == null || job.getSegments().isEmpty()) {
            throw new ServiceException("打印任务为空");
        }

        boolean allSameOrientation = true;
        Boolean firstOrientation = null;
        for (ReportPrintJob.JobSegment segment : job.getSegments()) {
            if (firstOrientation == null) {
                firstOrientation = segment.isLandscape();
            } else if (!firstOrientation.equals(segment.isLandscape())) {
                allSameOrientation = false;
                break;
            }
        }
        if (allSameOrientation && firstOrientation != null) {
            return renderBatchToPdfBytes(job.getSegments(), firstOrientation, job);
        }

        boolean originUseWatermark = job.isUseWatermark();
        boolean originUseSeal = job.isUseSeal();
        String originWatermarkText = job.getWatermarkText();
        Long companyId = job.getCompanyId();

        job.useFooterPageNumber(false);
        job.useWatermark(false);
        job.useSeal(false);
        List<byte[]> pdfParts = new ArrayList<>();
        List<ReportPrintJob.JobSegment> currentBatch = new ArrayList<>();
        Boolean currentLandscape = null;

        for (ReportPrintJob.JobSegment segment : job.getSegments()) {
            boolean isLandscape = segment.isLandscape();

            // 如果当前批次为空，初始化方向
            if (currentLandscape == null) {
                currentLandscape = isLandscape;
            }

            // 如果方向改变，先渲染当前批次
            if (isLandscape != currentLandscape) {
                if (!currentBatch.isEmpty()) {
                    pdfParts.add(renderBatchToPdfBytes(currentBatch, currentLandscape, job));
                    currentBatch.clear();
                }
                currentLandscape = isLandscape;
            }

            currentBatch.add(segment);
        }

        // 渲染剩余批次
        if (!currentBatch.isEmpty()) {
            pdfParts.add(renderBatchToPdfBytes(currentBatch, currentLandscape, job));
        }

        byte[] merged;
        if (pdfParts.size() == 1) {
            merged = pdfParts.get(0);
        } else {
            merged = mergePdfs(pdfParts);
        }

        byte[] pdf = PdfWatermarkUtil.addPageNumbers(merged);

        if (originUseWatermark) {
            String text = StrUtil.isBlank(originWatermarkText) ? appName : originWatermarkText;
            pdf = PdfWatermarkUtil.addWatermark(pdf, text);
        }

        if (originUseSeal) {
            byte[] sealBytes = loadCompanySeal(companyId);
            if (sealBytes != null) {
                pdf = PdfWatermarkUtil.addSealBottomRight(pdf, sealBytes);
            }
        }

        return pdf;
    }

    public String renderJobToHtml(ReportPrintJob job) {
        if (job == null || job.getSegments().isEmpty()) {
            throw new ServiceException("打印任务为空");
        }
        Boolean currentLandscape = null;
        List<ReportPrintJob.JobSegment> currentBatch = new ArrayList<>();

        for (ReportPrintJob.JobSegment segment : job.getSegments()) {
            boolean isLandscape = segment.isLandscape();
            if (currentLandscape == null) {
                currentLandscape = isLandscape;
            }
            if (isLandscape != currentLandscape) {
                throw new ServiceException("验真快照暂不支持混合布局报表");
            }
            currentBatch.add(segment);
        }
        if (currentLandscape == null) {
            throw new ServiceException("打印任务为空");
        }
        return renderBatchToHtml(currentBatch, currentLandscape);
    }

    private byte[] renderBatchToPdfBytes(List<ReportPrintJob.JobSegment> segments, boolean isLandscape, ReportPrintJob job) {
        if (segments.isEmpty()) return new byte[0];

        String html = renderBatchToHtml(segments, isLandscape);
        return generatePdf(html, job, isLandscape);
    }

    private String renderBatchToHtml(List<ReportPrintJob.JobSegment> segments, boolean isLandscape) {
        if (segments == null || segments.isEmpty()) {
            return "";
        }

        // 先顺序计算标题，保证与原逻辑一致（第一个非空标题）
        String batchTitle = "";
        for (ReportPrintJob.JobSegment segment : segments) {
            String title = segment.getProvider().getReportTitle(segment.getParams());
            if (StrUtil.isNotBlank(title)) {
                batchTitle = title;
                break;
            }
        }

        // 小批量直接串行，避免线程切换和调度开销
        String[] renderedBodies = new String[segments.size()];
        if (segments.size() < Math.max(1, renderParallelThreshold) || renderExecutor == null) {
            for (int i = 0; i < segments.size(); i++) {
                renderedBodies[i] = renderSegmentBody(segments.get(i));
            }
        } else {
            List<CompletableFuture<Void>> futures = new ArrayList<>(segments.size());
            for (int i = 0; i < segments.size(); i++) {
                final int index = i;
                CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> renderSegmentBody(segments.get(index)), renderExecutor)
                        .thenAccept(body -> renderedBodies[index] = body);
                futures.add(future);
            }
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        }

        List<String> bodies = new ArrayList<>(segments.size());
        for (String body : renderedBodies) {
            if (body != null) {
                bodies.add(body);
            }
        }

        String layoutCode = isLandscape
                ? "templates/layout/printLayoutLandscape.html.vm"
                : "templates/layout/printLayout.html.vm";

        VelocityContext layoutContext = new VelocityContext();
        layoutContext.put("tool", new ReportVelocityTool());
        layoutContext.put("title", batchTitle);
        layoutContext.put("bodies", bodies);

        return renderTemplate(layoutCode, layoutContext);
    }

    private String renderSegmentBody(ReportPrintJob.JobSegment segment) {
        IReportDataProvider provider = segment.getProvider();
        Map<String, Object> data = provider.prepareData(segment.getParams());
        if (data == null) {
            return null;
        }
        VelocityContext context = new VelocityContext(data);
        context.put("tool", new ReportVelocityTool());
        String body = renderTemplate(provider.getTemplateCode(), context);
        return extractBodyContent(body);
    }

    private byte[] mergePdfs(List<byte[]> pdfBytesList) {
        if (pdfBytesList == null || pdfBytesList.isEmpty()) {
            return new byte[0];
        }

        PDFMergerUtility merger = new PDFMergerUtility();
        try (ByteArrayOutputStream mergedOut = new ByteArrayOutputStream()) {
            merger.setDestinationStream(mergedOut);

            for (byte[] pdf : pdfBytesList) {
                // PDFBox 2.0+ 推荐使用 load
                merger.addSource(new ByteArrayInputStream(pdf));
            }

            // 使用内存合并，如果内存溢出风险大，可以改为 setupTempFileOnly()
            merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
            return mergedOut.toByteArray();
        } catch (Exception e) {
            log.error("Failed to merge PDFs", e);
            throw new ServiceException("PDF合并失败: " + e.getMessage());
        }
    }

    /**
     * 批量渲染报表并合并
     * <p>
     * 使用 Layout 模版进行合并
     * </p>
     *
     * @param provider   数据提供者
     * @param paramsList 参数列表（每个 Map 对应一个报表实例）
     * @param layoutCode 布局模版编码（如 "templates/layout/printLayout.html.vm"）
     * @return 合并后的 HTML
     */
    public String renderBatch(IReportDataProvider provider, List<Map<String, Object>> paramsList, String layoutCode) {
        List<String> bodies = new ArrayList<>();
        String templateCode = provider.getTemplateCode();

        for (Map<String, Object> params : paramsList) {
            Map<String, Object> data = provider.prepareData(params);
            if (data == null) {
                continue;
            }
            VelocityContext context = new VelocityContext(data);
            context.put("tool", new ReportVelocityTool());

            // Render body only
            String body = renderTemplate(templateCode, context);
            bodies.add(extractBodyContent(body));
        }

        if (StrUtil.isBlank(layoutCode)) {
            layoutCode = provider.isLandscape()
                    ? "templates/layout/printLayoutLandscape.html.vm"
                    : "templates/layout/printLayout.html.vm";
        }

        VelocityContext layoutContext = new VelocityContext();
        layoutContext.put("tool", new ReportVelocityTool());
        layoutContext.put("title", provider.getReportTitle());
        layoutContext.put("bodies", bodies);

        return renderTemplate(layoutCode, layoutContext);
    }

    /**
     * 批量渲染并生成 PDF (包含水印和公章)
     *
     * @param provider  数据提供者
     * @param ids       业务ID列表
     * @param idKey     业务ID参数名
     * @param companyId 公司ID (用于公章)
     * @return PDF 字节数组
     */
    public byte[] renderBatchToPdf(IReportDataProvider provider, List<String> ids, String idKey, Long companyId) {
        // 创建临时 Job 以复用 generatePdf 逻辑
        ReportPrintJob tempJob = new ReportPrintJob().withCompanyId(companyId).useSeal(true).useWatermark(true);

        List<Map<String, Object>> paramsList = new ArrayList<>();
        if (ids != null) {
            for (String id : ids) {
                paramsList.add(Map.of(idKey, id));
            }
        }

        // 渲染 HTML
        String html = renderBatch(provider, paramsList, null);

        // 转 PDF
        return generatePdf(html, tempJob, provider.isLandscape());
    }

    /**
     * 生成 PDF (私有方法)
     */
    private byte[] generatePdf(String htmlContent, ReportPrintJob job, boolean isLandscape) {
        try {
            String footer = """
                    <div style="font-size:10px;color:#000;width:100%;padding:0 1cm;">
                        <span style="float:left;color:#888;">平台信息依实为准，信息使用自判</span>
                        <span style="float:right;">第 <span class='pageNumber'></span> / <span class='totalPages'></span> 页</span>
                    </div>
                    """;

            // 性能优化：直接以内存HTML调用 Gotenberg，避免频繁临时文件 IO
            byte[] pdf = gotenbergClient.convertHtmlToPdf(htmlContent, job.isUseFooterPageNumber() ? footer : null, isLandscape);

            // Add Watermark
            if (job.isUseWatermark()) {
                String text = StrUtil.isBlank(job.getWatermarkText()) ? appName : job.getWatermarkText();
                if (isLandscape) {
                    pdf = PdfWatermarkUtil.addWatermarkLandscape(pdf, text);
                } else {
                    pdf = PdfWatermarkUtil.addWatermark(pdf, text);
                }
            }

            // Add Seal
            if (job.isUseSeal()) {
                byte[] sealBytes = loadCompanySeal(job.getCompanyId());
                if (sealBytes != null) {
                    return PdfWatermarkUtil.addSealBottomRight(pdf, sealBytes);
                }
            }
            return pdf;

        } catch (Exception e) {
            log.error("Failed to generate PDF", e);
            throw new ServiceException("生成PDF失败: " + e.getMessage());
        }
    }

    private byte[] loadCompanySeal(Long companyId) {
        if (companyId == null) {
            return null;
        }

        byte[] cached = companySealCache.get(companyId);
        if (cached != null) {
            return cached;
        }

        try {
            SysCompanyVo company = sysCompanyService.queryById(companyId);
            if (company == null) {
                return null;
            }
            String seal = company.getSealUrl();
            if (StringUtils.isBlank(seal)) {
                return null;
            }
            String trimmed = seal.trim();
            byte[] sealBytes;
            if (trimmed.matches("^\\d+(,\\d+)*$")) {
                List<OssDTO> list = ossService.selectByIds(trimmed);
                if (list == null || list.isEmpty()) {
                    return null;
                }
                String url = list.get(0).getUrl();
                sealBytes = downloadBytes(url);
            } else {
                sealBytes = downloadBytes(trimmed);
            }

            if (sealBytes != null) {
                companySealCache.put(companyId, sealBytes);
            }
            return sealBytes;
        } catch (Exception e) {
            log.warn("Failed to load company seal for companyId: {}", companyId, e);
            // 公章加载失败不应阻塞主流程，返回 null 即可
            return null;
        }
    }

    private byte[] downloadBytes(String url) {
        if (StringUtils.isBlank(url)) {
            return null;
        }
        try {
            java.net.URL u = new java.net.URL(url);
            try (java.io.InputStream is = u.openStream()) {
                return is.readAllBytes();
            }
        } catch (Exception e) {
            log.error("Failed to download seal image from url: {}", url, e);
            return null;
        }
    }

    private String extractBodyContent(String html) {
        if (StrUtil.isBlank(html)) return "";
        String lower = html.toLowerCase();
        int start = lower.indexOf("<body");
        int end = lower.lastIndexOf("</body>");
        if (start >= 0 && end >= 0) {
            start = lower.indexOf(">", start) + 1;
            return html.substring(start, end);
        }
        return html;
    }

    /**
     * 渲染指定模版
     */
    public String renderTemplate(String templateCode, VelocityContext context) {
        try {
            // 确保 context 中有 tool
            if (!context.containsKey("tool")) {
                context.put("tool", new ReportVelocityTool());
            }
            
            Template template = velocityEngine.getTemplate(templateCode);
            StringWriter writer = new StringWriter();
            template.merge(context, writer);
            return writer.toString();
        } catch (Exception e) {
            log.error("Failed to render template: {}", templateCode, e);
            throw new ServiceException("报表渲染失败: " + e.getMessage());
        }
    }

    /**
     * 仅渲染内容（用于预览等直接调用）
     */
    public String renderContent(String templateCode, Map<String, Object> data) {
        VelocityContext context = new VelocityContext(data);
        return renderTemplate(templateCode, context);
    }

    /**
     * 渲染字符串模版（用于在线编辑预览）
     *
     * @param templateContent 模版内容
     * @param data            数据
     * @return 渲染结果
     */
    public String renderString(String templateContent, Map<String, Object> data) {
        VelocityContext context = new VelocityContext(data);
        if (!context.containsKey("tool")) {
            context.put("tool", new ReportVelocityTool());
        }
        
        StringWriter writer = new StringWriter();
        try {
            velocityEngine.evaluate(context, writer, "preview", templateContent);
            return writer.toString();
        } catch (Exception e) {
            log.error("Failed to render string template", e);
            throw new ServiceException("预览渲染失败: " + e.getMessage());
        }
    }
}
