package me.qwq.doghouse.flexmark.ext.mermaid;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.parser.Parser;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.html.HtmlRendererOptions;
import com.vladsch.flexmark.html.HtmlWriter;
import com.vladsch.flexmark.html.renderer.NodeRenderer;
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.html.Attribute;
import com.vladsch.flexmark.util.misc.CharPredicate;
import com.vladsch.flexmark.util.sequence.BasedSequence;

import lombok.extern.slf4j.Slf4j;
import me.qwq.doghouse.config.DoghouseProperties;
import me.qwq.doghouse.pojo.bo.ThemeInfo;
import me.qwq.doghouse.service.ThemeService;
import me.qwq.doghouse.util.Cryptos;
import me.qwq.doghouse.util.OSUtils;
import me.qwq.doghouse.util.SpringContextHolder;

@Slf4j
public class MermaidNodeRenderer implements NodeRenderer {

    final MermaidOptions options;
    final static String MERMAID = "mermaid";
    final static ResourceLoader RESOURCE_LOADER =
            new DefaultResourceLoader();

    public MermaidNodeRenderer(DataHolder options) {
        this.options = new MermaidOptions(options);
    }

    @Override
    public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
        Set<NodeRenderingHandler<?>> set = new HashSet<>();
        set.add(new NodeRenderingHandler<>(FencedCodeBlock.class, MermaidNodeRenderer.this::render));
        return set;
    }

    /**
     * 直接渲染 &lt;div class="mermaid"> 的块
     * 
     * @param node
     * @param html
     * @param htmlOptions
     */
    private void renderPlainDiv(FencedCodeBlock node, HtmlWriter html, HtmlRendererOptions htmlOptions) {
        html.line().srcPosWithTrailingEOL(node.getChars()).attr(Attribute.CLASS_ATTR, MERMAID).withAttr().tag("pre")
                .text(node.getContentChars().normalizeEOL()).tag("/pre").lineIf(htmlOptions.htmlBlockCloseTagEol);
    }

    /**
     * 渲染 SVG
     * 
     * @param node
     * @param html
     * @param htmlOptions
     * @param svgFileContent
     */
    private void renderSvg(FencedCodeBlock node, HtmlWriter html, HtmlRendererOptions htmlOptions,
            String svgFileContent) {
        html.line().srcPosWithTrailingEOL(node.getChars()).attr(Attribute.CLASS_ATTR, MERMAID)
                .attr("data-processed", "true").withAttr().tag("div").raw(svgFileContent).tag("/div")
                .lineIf(htmlOptions.htmlBlockCloseTagEol);
    }

    private void render(FencedCodeBlock node, NodeRendererContext context, HtmlWriter html) {
        HtmlRendererOptions htmlOptions = context.getHtmlOptions();
        // BasedSequence language =
        // node.getInfoDelimitedByAny(htmlOptions.languageDelimiterSet);
        BasedSequence language = node.getInfoDelimitedByAny(CharPredicate.ANY_EOL);

        if (language.startsWithIgnoreCase(options.mermaidLanguage)) {

            // 获取内部代码块
            String innerMermaidCode = node.getContentChars().normalizeEOL();

            // 根据代码块内容计算 MD5
            String md5 = Cryptos.md5(innerMermaidCode);

            // 加锁阻止并发
            synchronized (("mermaid-" + md5).intern()) {

                DoghouseProperties doghouseProperties = SpringContextHolder.getBean("doghouseProperties");
                Path tmpPath = Paths.get(doghouseProperties.getWorkPath(), "tmp/mermaid");

                Path svgFilePath = tmpPath.resolve(md5 + ".svg");
                File svgFile = svgFilePath.toFile();

                if (!svgFile.exists()) {
                    // 重新渲染 svg

                    File directory = tmpPath.toFile();
                    if (!directory.exists()) {
                        directory.mkdirs();
                    } else if (!directory.isDirectory()) {
                        log.warn("Mermaid temp path {} is not a directory!", tmpPath.toString());
                        renderPlainDiv(node, html, htmlOptions);
                        return;
                    }

                    // 写 mermaid 文件
                    Path mmdFilePath = tmpPath.resolve(md5 + ".mmd");
                    File mmdFile = mmdFilePath.toFile();
                    if (!mmdFile.exists()) {
                        try {
                            Files.writeString(mmdFilePath, innerMermaidCode);
                        } catch (Exception e) {
                            log.warn("Error writing to mermaid mid file: {}\r\n{}", mmdFile.getPath(), innerMermaidCode,
                                    e);
                            renderPlainDiv(node, html, htmlOptions);
                            return;
                        }
                    }
                    // mmdc -i {md5}.mmd -o {md5}.svg -b transparent -I mermaid-{md5}
                    List<String> cmd = new ArrayList<>();
                    cmd.add("mmdc");
                    cmd.add("-i");cmd.add(mmdFilePath.toString());  // 输入文件路径
                    cmd.add("-o");cmd.add(svgFilePath.toString());  // 输出文件路径
                    cmd.add("-b");cmd.add("transparent");           // 透明背景色
                    cmd.add("-I");cmd.add("mermaid-" + md5);        // 以 mermaid-{md5} 作为元素 ID

                    
                    // 提供默认文件、主题配套文件并释放
                    ThemeService themeService = SpringContextHolder.getBean(ThemeService.class);
                    ThemeInfo theme = themeService.getCurrentTheme();
                    
                    Path configPath = null;
                    
                    if (theme.isInternal()) {
                        // 内部文件要释放到临时目录使用
                        Resource configResource = RESOURCE_LOADER.getResource("classpath:templates/" + theme.getTemplateString("conf/mermaid/config.json"));
                        if (configResource.exists()) {
                            configPath = tmpPath.resolve("config.json");
                            try (InputStream in = configResource.getInputStream()) {
                                Files.createDirectories(configPath.getParent());
                                Files.copy(in, configPath, StandardCopyOption.REPLACE_EXISTING);
                            }
                            catch (Exception e) {
                                log.warn("Mermaid config found in internal theme but failed to copy to local path: {}", configPath.toString(), e);
                            }
                        }
                    }
                    else {
                        configPath = Paths.get(themeService.getCurrentTheme().getDirectory(), "conf/mermaid/config.json");
                    }
                    
                    if (configPath != null) {
                        File configFile = configPath.toFile();
                        if (configFile.exists()) {
                            cmd.add("-c");
                            cmd.add(configPath.toString());
                        }
                    }
                    
                    try {
                        
                        if (OSUtils.isWindows()) {
                            cmd.addFirst("/C");
                            cmd.addFirst("/S");
                            cmd.addFirst("cmd.exe");
                        }
                        
                        ProcessBuilder pb = new ProcessBuilder(cmd);
                        pb.redirectErrorStream(true);
                        Process process = pb.start();

                        try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                            String line;
                            while ((line = reader.readLine()) != null) {
                                log.debug("[mermaid-encode] {}", line);
                            }
                        }

                        int exitCode = process.waitFor(15, TimeUnit.SECONDS) ? process.exitValue() : -1;

                        if (exitCode == 0) {
                            if (!svgFile.exists()) {
                                log.warn("Mermaid CLI finished with code 0, but output file not found: {}",
                                        svgFile.toString());
                            } else {
                                log.info("Mermaid CLI executed successfully.");
                            }
                        } else {
                            log.warn("Mermaid CLI execution failed with exit code: {}", exitCode);
                        }
                    } catch (IOException | InterruptedException e) {
                        log.warn("Mermaid CLI execution failed with error", e);
                    }
                    try {
                        Files.delete(mmdFilePath);
                    } catch (IOException e) {
                    }
                }

                if (!svgFile.exists()) {
                    // 最终还是不存在, 直接原样返回到前端, 让前端自行渲染
                    renderPlainDiv(node, html, htmlOptions);
                    return;
                }

                try {
                    String svgContent = Files.readString(svgFilePath);
                    if (!isValidSvg(svgContent)) {
                        // illegal svg file
                        log.warn("Illegal svg content, ignore and delete.\r\n{}", svgContent);
                        Files.delete(svgFilePath);
                        renderPlainDiv(node, html, htmlOptions);
                    }
                    renderSvg(node, html, htmlOptions, svgContent);
                } catch (Exception e) {
                    log.warn("Failed to load mermaid rendered svg: {}", svgFile.getPath(), e);
                    renderPlainDiv(node, html, htmlOptions);
                }
            }
        } else {
            context.delegateRender();
        }
    }

    public static class Factory implements NodeRendererFactory {
        @NotNull
        @Override
        public NodeRenderer apply(@NotNull DataHolder options) {
            return new MermaidNodeRenderer(options);
        }
    }

    /**
     * 判断是否合法 svg 内容
     * 
     * @param svgContent
     * @return
     */
    public static boolean isValidSvg(String svgContent) {
        if (StringUtils.isBlank(svgContent)) {
            return false;
        }

        try {
            // 解析为 XML 模式，避免 HTML parser 改标签大小写
            org.jsoup.nodes.Document doc = Jsoup.parse(svgContent, "", Parser.xmlParser());

            // 1）必须有根 svg
            org.jsoup.select.Elements svgElements = doc.getElementsByTag("svg");
            if (svgElements.isEmpty()) {
                return false;
            }

            // 2）禁止 script
            if (!doc.getElementsByTag("script").isEmpty()) {
                return false;
            }

            // 3）可选：禁止外部图像/资源（安全性增强）
            if (!doc.select("[xlink:href^=http], [href^=http]").isEmpty()) {
                return false;
            }

            // 4）可选：节点内容不能完全为空（避免 `<svg/>`）
            String inner = svgElements.first().html().trim();
            if (inner.isEmpty()) {
                return false;
            }

            return true;

        } catch (Exception e) {
            // Jsoup 解析失败 → 非法 SVG
            return false;
        }
    }

}
