package org.example.Utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FormatFactoryUtil {
    private static final Logger log = LoggerFactory.getLogger(FormatFactoryUtil.class);

    // 对应 C# 的静态变量，使用 final 保证不可修改（若需动态修改可移除 final）
    public static final String ffmpegPath;
    public static final String ffprobePath;

    // 静态代码块初始化路径（对应 C# 静态变量的初始化逻辑）
    static {
        // 1. 获取服务器根路径（对应 C# WebConfigUtil.ServerPath）
        String serverPath = WebConfigUtil.getServerPath(); // 假设 WebConfigUtil 有 getServerPath() 方法

        // 2. 路径拼接：还原 Path.Combine(ServerPath, "bin", "ffmpeg\\ffmpeg.exe")
        // Java 用 File.separator 替代硬编码 "\"，保证 Windows 兼容性（File.separator 在 Windows 是 "\"，Linux 是 "/"）
        ffmpegPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), "ffmpeg.exe").getAbsolutePath();

        // 3. 拼接 ffprobe 路径：还原 Path.Combine(ServerPath, "bin", "ffmpeg\\ffprobe.exe")
        ffprobePath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), "ffprobe.exe").getAbsolutePath();
    }

    // 若需要兼容跨平台（可选）：自动根据系统调整可执行文件后缀（Windows 是 .exe，Linux/Mac 无后缀）
    public static final String ffmpegCrossPlatformPath;
    public static final String ffprobeCrossPlatformPath;

    static {
        String serverPath = WebConfigUtil.getServerPath();
        // 判断操作系统类型
        String os = System.getProperty("os.name").toLowerCase();
        boolean isWindows = os.contains("win");
        String ffmpegExe = isWindows ? "ffmpeg.exe" : "ffmpeg";
        String ffprobeExe = isWindows ? "ffprobe.exe" : "ffprobe";

        // 跨平台路径拼接
        ffmpegCrossPlatformPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), ffmpegExe).getAbsolutePath();
        ffprobeCrossPlatformPath = new File(new File(new File(serverPath, "bin"), "ffmpeg"), ffprobeExe).getAbsolutePath();
    }

    // 获取ffmpeg路径（从配置工具类获取，符合项目中WebConfigUtil的使用风格）
    private static String getFfmpegToolPath() {
        // 假设ffmpeg路径配置在WebConfig中，实际路径根据项目调整
        return WebConfigUtil_web.getServerPath() + "tools/FormatFactory/ffmpeg.exe";
    }

    /**
     * 执行外部进程
     *
     * @param exePath   外部程序绝对路径（如 ffmpeg.exe）
     * @param args      进程参数（命令行参数拼接字符串）
     * @param wait      是否阻塞等待进程结束（对应 C# wait 参数）
     * @param onExited  进程退出回调（对应 C# onExited，进程结束后执行）
     * @param onRunning 进程运行中回调（仅同步等待时执行，对应 C# onRunning）
     * @param success   进程成功判定函数（对应 C# success，返回 true 表示进程执行成功）
     * @return 执行结果：程序不存在返回 false；异常返回 false；成功返回 true
     */
    public static boolean runProcess(
            String exePath,
            String args,
            boolean wait,
            Runnable onExited,
            java.util.function.Consumer<Process> onRunning,
            java.util.function.Function<Process, Boolean> success
    ) {
        // 1. 验证程序是否存在（对应 C# File.Exists(exePath)）
        java.io.File exeFile = new java.io.File(exePath);
        if (!exeFile.exists()) {
            LoggerHandler.debug(new Object(), String.format("执行外部程序失败，未找到转换程序：%s", exePath));
            return false;
        }

        Process process = null;
        // 2. 构建进程启动器（对应 C# ProcessStartInfo）
        java.lang.ProcessBuilder processBuilder = new java.lang.ProcessBuilder();
        processBuilder.command(exePath, args); // 命令 + 参数（args 整体作为一个参数，若需拆分需手动处理）

        // 配置进程启动参数（还原 C# StartInfo 配置）
        processBuilder.redirectErrorStream(false); // 不合并错误流（C# RedirectStandardError = true 对应此配置）
        processBuilder.inheritIO(); // 不继承父进程 IO（对应 UseShellExecute = false）
        processBuilder.directory(exeFile.getParentFile()); // 进程工作目录设为程序所在目录

        try {
            // 3. 启动进程（对应 C# p.Start()）
            process = processBuilder.start();

            // 4. 异步捕获错误流（FFMPEG 输出通过错误流返回，还原 C# ErrorDataReceived 逻辑）
            captureErrorStream(process.getErrorStream());

            // 5. 异步捕获标准输出流（还原 C# RedirectStandardOutput = true）
            captureStandardOutput(process.getInputStream());

            // 6. 进程退出回调（对应 C# p.Exited += onExited）
            if (onExited != null) {
                Process finalProcess = process;
                new Thread(() -> {
                    try {
                        finalProcess.waitFor(); // 等待进程结束后执行回调
                        onExited.run();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        LoggerHandler.error(new Object(), "进程退出回调执行失败", e);
                    }
                }).start();
            }

            // 7. 同步等待进程结束（对应 C# wait = true 逻辑）
            if (wait) {
                // 执行运行中回调（对应 C# onRunning(p)）
                if (onRunning != null) {
                    onRunning.accept(process);
                }
                // 阻塞等待进程结束（对应 C# p.WaitForExit()）
                process.waitFor();

                // 判定执行结果（对应 C# success != null ? success(p) : p.ExitCode == 0）
                if (success != null) {
                    return success.apply(process);
                } else {
                    return process.exitValue() == 0; // 退出码 0 表示成功
                }
            }

            // 异步执行时直接返回 true（对应 C# wait = false 逻辑）
            return true;

        } catch (IOException e) {
            LoggerHandler.error(new Object(), String.format("执行外部程序%s失败，参数：%s", exePath, args), e);
            return false;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            LoggerHandler.error(new Object(), String.format("进程执行被中断，程序：%s，参数：%s", exePath, args), e);
            return false;
        } finally {
            // 8. 释放进程资源（对应 C# p.Close() + p.Dispose()）
            if (process != null) {
                process.destroy(); // 销毁进程
            }
        }
    }

    /**
     * 捕获进程错误流（FFMPEG 核心输出流，还原 C# ErrorDataReceived 事件）
     *
     * @param errorStream 进程错误流
     */
    private static void captureErrorStream(InputStream errorStream) {
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(errorStream, StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    // 此处可添加错误流日志打印（对应 C# Output 方法逻辑）
                    // LoggerHandler.debug(new Object(), "FFMPEG 错误流输出：" + line);
                }
            } catch (IOException e) {
                LoggerHandler.error(new Object(), "捕获进程错误流失败", e);
            }
        }).start();
    }

    /**
     * 捕获进程标准输出流（还原 C# RedirectStandardOutput = true）
     *
     * @param outputStream 进程标准输出流
     */
    private static void captureStandardOutput(InputStream outputStream) {
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(outputStream, StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    // 此处可添加标准输出流日志打印
                    // LoggerHandler.debug(new Object(), "进程标准输出：" + line);
                }
            } catch (IOException e) {
                LoggerHandler.error(new Object(), "捕获进程标准输出流失败", e);
            }
        }).start();
    }

    /**
     * 日志工具类适配（还原 C# LoggerHandler 逻辑，需确保项目中存在该类）
     */
    public static class LoggerHandler {
        public static void debug(Object sender, String message) {
            // 适配项目实际日志框架（如 Log4j、SLF4J）
            log.debug(String.valueOf("[DEBUG] " + message));
        }

        public static void error(Object sender, String message, Throwable throwable) {
            // 适配项目实际日志框架
            if (throwable == null) {
                log.error(String.valueOf(message));
                return;
            }
            log.error(String.valueOf(message), throwable);
        }
    }

    /**
     * 视频格式转换
     *
     * @param sourceFilePath 源视频文件路径
     * @param outPutFilePath 输出视频文件路径
     * @param wait           是否阻塞等待转换完成（默认 true）
     * @param delOldFile     转换完成后是否删除原文件（默认 true）
     * @return 转换成功返回 true；文件不存在/转换失败返回 false
     */
    public static boolean convertVideo(String sourceFilePath, String outPutFilePath, boolean wait, boolean delOldFile) {
        // 1. 验证源文件是否存在（对应 C# File.Exists(sourceFilePath)）
        File sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            return false;
        }

        // 2. 构建 ffmpeg 命令参数（严格还原 C# 参数格式）
        // 参数说明：-i 源文件 -y 覆盖输出文件 -b 1024k 视频比特率 -acodec copy 复制音频编码
        String args = String.format(
                "-i \"%s\" -y \"%s\" -b 1024k -acodec copy",
                sourceFilePath,
                outPutFilePath
        );

        // 3. 调用外部 ffmpeg 进程（还原 C# RunProcess 逻辑及回调）
        return runProcess(
                ffmpegPath,
                args,
                wait,
                // 进程退出回调：转换完成后删除原文件（对应 C# onExited 委托）
                () -> {
                    if (delOldFile) {
                        File oldFile = new File(sourceFilePath);
                        if (oldFile.exists()) {
                            oldFile.delete(); // 对应 C# File.Delete(sourceFilePath)
                        }
                    }
                },
                null, // onRunning 回调为 null（与 C# 一致）
                null  // success 判定为 null（使用默认退出码判定：0 为成功）
        );
    }

    // 重载方法：默认 wait=true、delOldFile=true（还原 C# 可选参数特性）
    public static boolean convertVideo(String sourceFilePath, String outPutFilePath) {
        return convertVideo(sourceFilePath, outPutFilePath, true, true);
    }

    /**
     * 处理FFmpeg输出信息（对应C#的Output方法）
     */
    private static void Output(String outputLine) {
        if (outputLine == null || outputLine.isEmpty()) {
            return;
        }
        // 可根据需要添加输出解析逻辑（如进度、时长等）
        // 参考C#注释中的正则解析逻辑，可结合项目中的NativeExtensionUtils工具类处理
    }

    /**
     * MP4 编码转换（兼容浏览器播放）
     *
     * @param inputPath  输入视频路径
     * @param outputPath 输出视频路径
     * @param wait       是否阻塞等待转换完成（默认 true）
     * @param delOldFile 转换完成后是否删除原文件（默认 true）
     * @return 转换成功返回 true；文件不存在/无需转换/失败返回 false
     */
    public static boolean convertMp4Code(String inputPath, String outputPath, boolean wait, boolean delOldFile) {
        // 1. 验证输入文件是否存在（对应 C# File.Exists(inputPath)）
        File inputFile = new File(inputPath);
        if (!inputFile.exists()) {
            return false;
        }

        // 2. 检查是否包含专利编码（无需转换则直接返回 true）
        if (!mp4HasPatentCodec(inputPath)) {
            return true;
        }

        // 3. 构建 ffmpeg 命令参数（严格还原 C# 参数格式）
        // 注意：Java 字符串转义需用双引号，路径包含空格时自动适配
        String args = String.format(
                "-i \"%s\" -c:v libx264 -profile:v high -preset slow -crf 23 -c:a aac -b:a 128k \"%s\"",
                inputPath,
                outputPath
        );

        // 4. 调用外部进程（对应 C# RunProcess，还原回调逻辑）
        return runProcess(
                ffmpegPath,
                args,
                wait,
                // 进程退出回调：删除原文件（对应 C# onExited 委托）
                () -> {
                    if (delOldFile) {
                        File oldFile = new File(inputPath);
                        if (oldFile.exists()) {
                            oldFile.delete(); // 对应 C# File.Delete(inputPath)
                        }
                    }
                },
                null, // onRunning 回调为 null（与 C# 一致）
                null  // success 判定为 null（使用默认退出码判定）
        );
    }

    /**
     * 检测视频是否包含专利编码
     *
     * @param videoPath 视频文件路径
     * @return 包含专利编码返回 true；否则返回 false
     */
    public static boolean mp4HasPatentCodec(String videoPath) {
        // 1. 构建 ffprobe 命令参数（严格还原 C# 参数）
        String args = String.format(
                "-v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 \"%s\"",
                videoPath
        );

        // 2. 原子引用存储输出结果（Java 多线程安全，对应 C# 闭包变量 output）
        AtomicReference<String> outputRef = new AtomicReference<>("");

        // 3. 调用外部进程（还原 C# RunProcess 回调逻辑）
        boolean processSuccess = runProcess(
                ffprobePath,
                args,
                true, // 必须同步等待（wait=true）
                null, // onExited 为 null
                // onRunning 回调：读取标准输出流（对应 C# p.StandardOutput.ReadToEnd()）
                process -> {
                    try (java.io.BufferedReader reader = new java.io.BufferedReader(
                            new java.io.InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) {
                        // 读取所有输出（对应 C# ReadToEnd()）
                        StringBuilder outputSb = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            outputSb.append(line);
                        }
                        outputRef.set(outputSb.toString().trim());
                    } catch (java.io.IOException e) {
                        LoggerHandler.error(new Object(), "读取 ffprobe 输出失败", e);
                        outputRef.set("");
                    }
                },
                // success 判定：正则匹配专利编码（还原 C# Regex.IsMatch）
                process -> {
                    String output = outputRef.get();
                    // 正则表达式：忽略大小写，匹配 h264/mpeg4/mpeg2/vc1/hevc（与 C# 一致）
                    Pattern pattern = Pattern.compile("(h264|mpeg4|mpeg2|vc1|hevc)", Pattern.CASE_INSENSITIVE);
                    Matcher matcher = pattern.matcher(output);
                    return matcher.find();
                }
        );

        // 4. 进程执行成功且正则匹配成功，返回 true（与 C# 逻辑一致）
        return processSuccess;
    }

    /**
     * 获取视频时长（单位：秒）
     *
     * @param videoPath 视频文件路径
     * @return 成功返回时长（秒）；失败返回 0
     */
    public static double getVideoTime(String videoPath) {
        // 1. 构建 ffprobe 命令参数（JSON 格式输出视频信息，与 C# 一致）
        String args = String.format(
                "-v quiet -print_format json -show_format \"%s\"",
                videoPath
        );

        // 2. 原子引用存储 JSON 输出（多线程安全）
        AtomicReference<String> outputRef = new AtomicReference<>("");

        // 3. 调用外部进程（同步等待，读取输出）
        boolean processSuccess = runProcess(
                ffprobePath,
                args,
                true, // wait=true 同步等待
                null, // onExited 为 null
                // onRunning 回调：读取标准输出流（对应 C# p.StandardOutput.ReadToEnd()）
                process -> {
                    try (java.io.BufferedReader reader = new java.io.BufferedReader(
                            new java.io.InputStreamReader(process.getInputStream(), java.nio.charset.StandardCharsets.UTF_8))) {
                        StringBuilder outputSb = new StringBuilder();
                        String line;
                        while ((line = reader.readLine()) != null) {
                            outputSb.append(line);
                        }
                        outputRef.set(outputSb.toString().trim());
                    } catch (java.io.IOException e) {
                        LoggerHandler.error(new Object(), "读取 ffprobe 视频信息失败", e);
                        outputRef.set("");
                    }
                },
                null // success 为 null（使用默认退出码判定）
        );

        // 4. 解析 JSON 数据（还原 C# JSON.Decode + Hashtable 逻辑）
        if (processSuccess) {
            String jsonOutput = outputRef.get();
            if (!jsonOutput.isEmpty()) {
                try {
                    // 解析 JSON 为 Map（对应 C# Hashtable）
                    Map<String, Object> infoMap = (Map<String, Object>) JSON.Decode(jsonOutput);
                    // 获取 format -> duration（对应 C# (info["format"] as Hashtable)["duration"]）
                    Map<String, Object> formatMap = (Map<String, Object>) infoMap.get("format");
                    if (formatMap != null && formatMap.containsKey("duration")) {
                        String durationStr = formatMap.get("duration").toString();
                        // 转换为 double（对应 C# ToDouble()）
                        return Double.parseDouble(durationStr);
                    }
                } catch (Exception e) {
                    LoggerHandler.error(new Object(), "解析视频时长 JSON 失败", e);
                }
            }
        }

        // 5. 失败返回 0（与 C# 一致）
        return 0.0;
    }

    // 重载方法：默认 wait=true、delOldFile=true（还原 C# 可选参数）
    public static boolean convertMp4Code(String inputPath, String outputPath) {
        return convertMp4Code(inputPath, outputPath, true, true);
    }
}
