package cn.iocoder.byzs.module.infra.framework.file.core.client.local;

import cn.hutool.core.io.FileUtil;
import cn.iocoder.byzs.module.infra.framework.file.core.client.AbstractFileClient;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 本地文件客户端
 *
 * @author 博雅智算源码
 */
public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {

    // 动态获取系统CPU核心数
    private static final int CPU_CORES = Runtime.getRuntime().availableProcessors();
    // 计算线程数，确保至少为1（处理CPU核心数为1的边界情况）
    private static final int THREAD_COUNT = Math.max(1, CPU_CORES / 2);

    // 线程池用于异步处理视频转码
    private static final ExecutorService TRANSCODE_EXECUTOR = new ThreadPoolExecutor(
            THREAD_COUNT, // 核心线程数，系统CPU核心数的一半
            THREAD_COUNT, // 最大线程数，系统CPU核心数的一半
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(20), // 任务队列，限制等待任务数
            new ThreadFactory() {
                private final AtomicInteger counter = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "video-transcode-" + counter.incrementAndGet());
                    thread.setDaemon(true);
                    return thread;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略：当队列满时，由调用者执行
    );

    public LocalFileClient(Long id, LocalFileClientConfig config) {
        super(id, config);
    }

    @Override
    protected void doInit() {
    }

    @Override
    public String ffmpegHts(String path) {
        // 将在lambda表达式中使用的变量声明为final
        final String finalPath = path;
        final String pathMp4 = getFilePath(path);
        String filePathName = pathMp4.substring(0, pathMp4.lastIndexOf("."));
        String fileName = filePathName.substring(filePathName.lastIndexOf("/"));
        final String pathM3u8 = filePathName + fileName;
        final String pathM3u8Name = pathM3u8 + ".m3u8";

        // 创建目录
        new File(filePathName).mkdirs();

        // 提交转码任务到线程池
        Future<String> future = TRANSCODE_EXECUTOR.submit(() -> {
            try {
                // 构建FFmpeg命令，添加资源限制参数
                ProcessBuilder processBuilder = new ProcessBuilder(
                        "ffmpeg", "-i", pathMp4,
                        "-c:v", "h264", "-c:a", "aac",
                        "-threads", "2", // 限制使用2个线程，根据需要调整
                        "-preset", "medium", // 编码预设
                        "-crf", "24", // 视频质量控制
                        "-hls_time", "10", "-hls_list_size", "0",
                        "-hls_segment_filename", pathM3u8 + "_%03d.ts",
                        pathM3u8Name
                );

                // 设置进程优先级（Linux/Mac）
                try {
                    if (System.getProperty("os.name").toLowerCase().contains("win")) {
                        // Windows系统设置优先级的方式不同
                    } else {
                        // Unix/Linux系统设置进程组优先级
                        processBuilder.environment().put("NICE", "10");
                    }
                } catch (Exception e) {
                    System.err.println("设置进程优先级失败: " + e.getMessage());
                }

                // 重定向错误流到输入流
                processBuilder.redirectErrorStream(true);

                // 启动进程
                final Process process = processBuilder.start();

                // Windows系统设置进程优先级
                if (System.getProperty("os.name").toLowerCase().contains("win")) {
                    try {
                        new ProcessBuilder("wmic", "process", "where", "ProcessId=" + process.pid(), "CALL", "setpriority", "below normal").start();
                    } catch (Exception e) {
                        System.err.println("设置Windows进程优先级失败: " + e.getMessage());
                    }
                }

                // 读取FFmpeg输出日志
                StringBuilder output = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(
                        new InputStreamReader(process.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        output.append(line).append("\n");
                    }
                }

                // 设置超时时间，避免无限等待
                boolean completed = process.waitFor(30, TimeUnit.MINUTES); // 30分钟超时
                if (!completed) {
                    process.destroyForcibly(); // 强制终止进程
                    throw new TimeoutException("视频转码超时");
                }

                int exitCode = process.exitValue();
                if (exitCode == 0) {
                    String dateStr = finalPath.substring(0, 8);
                    String finalPathM3u8Name = pathM3u8Name.substring(pathM3u8Name.lastIndexOf(dateStr));
                    System.out.println("视频切片成功，m3u8路径: " + finalPathM3u8Name);
                    return super.formatFileUrl(config.getDomain(), finalPathM3u8Name);
                } else {
                    System.err.println("FFmpeg执行失败，退出码: " + exitCode + "，输出: " + output.toString());
                    return null;
                }

            } catch (Exception e) {
                System.err.println("视频处理失败: " + e.getMessage());
                throw new RuntimeException("视频处理失败，请检查视频格式是否支持：", e);
            }
        });

        // 获取结果
        try {
            return future.get(35, TimeUnit.MINUTES); // 设置获取结果的超时时间
        } catch (Exception e) {
            future.cancel(true); // 取消任务
            throw new RuntimeException("视频转码处理超时或被取消", e);
        }
    }

    @Override
    public String upload(byte[] content, String path, String type) {
        // 执行写入
        String filePath = getFilePath(path);
        FileUtil.writeBytes(content, filePath);

        // 拼接返回路径
        return super.formatFileUrl(config.getDomain(), path);
    }

    @Override
    public void delete(String path) {
        String filePath = getFilePath(path);
        FileUtil.del(filePath);
    }

    @Override
    public byte[] getContent(String path) {
        String filePath = getFilePath(path);
        return FileUtil.readBytes(filePath);
    }

    public String getFilePath(String path) {
        return config.getBasePath() + File.separator + path;
    }

}