/// Streaming media-related processing, operated by means of ffmpeg
///
/// 使用该工具，需要先将ffmpeg配置到环境变量中，通过以下命令进行验证ffmpeg环境是否可用
///
/// ```bash
/// # 安装ffmpeg，官网下载地址：https://ffmpeg.org/download.html，让其ffmpeg、ffprobe命令可在全局使用
/// ffmpeg -version
/// ffprobe -version
/// ```
pub struct FfmpegUtil;

use crate::{CmdUtil, Exception, FileUtil};
use chrono::{Duration, NaiveDateTime};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

/// FFMPEG 的根路径，必须以路径分隔符结尾
static FFMPEG_ROOT_PATH: OnceLock<PathBuf> = OnceLock::new();

impl FfmpegUtil {

    /// 设置 FFMPEG 的根路径。
    ///
    /// 该函数只能被调用一次，后续调用会返回 `Err`。
    /// 路径会被规范化，确保其存在且以路径分隔符结尾。
    ///
    /// # Arguments
    ///
    /// * `ffmpeg_root_path` - FFMPEG 的根路径字符串。
    ///
    /// # Errors
    ///
    /// 如果路径不存在、不是目录，或者该函数已被调用过，会返回相应的错误。
    pub fn set_ffmpeg_root_path(ffmpeg_root_path: &str) -> Result<(), Exception>{
        // 1. 提前检查空字符串，返回明确的错误而不是 panic。
        if ffmpeg_root_path.is_empty() {
            return Err(Exception::ConfigError("FFmpeg root path cannot be empty.".into()));
        }

        // 2. 将 &str 转换为 PathBuf，便于后续的路径操作。
        let mut path = PathBuf::from(ffmpeg_root_path);

        // 3. 规范化路径：解析 . 和 ..，并去除多余的分隔符。
        //    这会让路径比较和后续拼接更可靠。
        path = path.canonicalize()?;

        // 4. 确保路径是一个目录。
        if !path.is_dir() {
            return Err(Exception::ConfigError(format!("FFmpeg root path '{}' is not a directory.", path.display()).into()));
        }

        // 5. 确保路径以平台相关的路径分隔符结尾。
        //    PathBuf 的 push 方法会自动处理分隔符，这是最安全的方式。
        path.push("");

        if !Path::new(ffmpeg_root_path).exists() {
            panic!("ffmpeg root path not exists");
        }
        // 6. 使用 OnceLock 的 set 方法，并返回 Result，而不是使用 expect。
        //    这将错误处理的责任交给了调用者，使函数更健壮。
        FFMPEG_ROOT_PATH
            .set(path.clone())
            .map_err(|_| Exception::ConfigError(format!("FFmpeg root path has already been set to '{}'.", path.display())))?;

        Ok(())

    }

    /// 安全地获取 FFMPEG 的根路径。
    pub fn try_get_ffmpeg_root_path() -> Option<&'static PathBuf> {
        FFMPEG_ROOT_PATH.get()
    }

    /// 获取视频文件的时长
    /// 
    /// 通过调用ffprobe命令行工具来获取指定视频文件的时长信息
    /// 
    /// # 参数
    /// * `video_path` - 视频文件的路径字符串引用
    /// 
    /// # 返回值
    /// * `u64` - 视频秒数
    /// * `Option<u64>` - 如果成功获取到视频时长则返回Some(时长秒数)，否则返回None
    pub fn video_seconds(video_path: &str) -> Option<u64> {
        // 如果手动设置了ffmpeg的路径，则使用指定的路径，如果没有指定，则任务使用环境变量中
        let root = FfmpegUtil::try_get_ffmpeg_root_path();
        let command = if let Some(root) = root {
            format!("{}{}", FileUtil::absolute_path_str(root.as_path()).unwrap(), "bin/ffprobe")
        } else {
            "ffprobe".to_string()
        };

        // 调用ffprobe命令获取视频时长信息
        let output = CmdUtil::run_cmd(vec![&command, "-i", video_path, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0"]);
        match output {
            Ok(seconds) => {
                // 解析命令行输出结果为u64类型
                let seconds: f64 = seconds.trim().parse().unwrap();
                let seconds = seconds.round() as u64;
                Some(seconds)
            },
            Err(e) => {
                println!("{}", e);
                None
            }
        }
    }

    /// 将秒数转换为SRT字幕文件所需的时间格式字符串
    /// 
    /// 该函数将输入的秒数转换为"小时:分钟:秒,毫秒"的格式，
    /// 其中毫秒部分固定为000，符合SRT字幕时间戳的格式要求。
    /// 
    /// # 参数
    /// * `seconds` - 需要转换的秒数
    /// 
    /// # 返回值
    /// 返回格式为"HH:MM:SS,000"的字符串，其中：
    /// - HH: 小时数，2位数字，不足时前面补0
    /// - MM: 分钟数，2位数字，不足时前面补0  
    /// - SS: 秒数，2位数字，不足时前面补0
    /// - 000: 毫秒数，固定为000
    pub fn srt_timestr(seconds: u64) -> String {
        // 整数除法自动向下取整
        let hours: u64 = seconds / 3600;
        let minutes: u64 = (seconds % 3600) / 60;
        let secs: u64 = seconds % 60;
        return format!("{:02}:{:02}:{:02},000", hours, minutes, secs)
    }

    /// 生成SRT字幕文件
    /// 
    /// 该函数根据给定的起始时间、持续秒数和文件路径生成SRT格式的字幕文件。
    /// 每一秒生成一个字幕条目，包含序号、时间轴和对应的时间文本。
    /// 
    /// # 参数
    /// * `start_time_text` - 起始时间字符串，格式为"YYYY-MM-DD HH:MM:SS"
    /// * `seconds` - 持续时间，以秒为单位
    /// * `srt_file_path` - 要生成的SRT文件路径
    /// 
    /// # 返回值
    /// 返回Result类型，成功时返回Ok(())，失败时返回包含错误信息的Err
    /// 
    /// # 错误
    /// 当时间解析失败、文件操作失败或写入失败时会返回相应的错误
    pub fn generate_srt(
        start_time_text: &str,
        seconds: u64,
        srt_file_path: &str,
    ) -> Result<(), Exception> {
        // 解析起始时间字符串
        let start_time = NaiveDateTime::parse_from_str(start_time_text, "%Y-%m-%d %H:%M:%S")?;
        // 创建并打开文件(如果文件已存在，则清空文件)
        let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(Path::new(srt_file_path))?;
        // 生成每一秒的字幕条目
        for i in 0..seconds {
            // 计算当前条目的起始时间
            let current_start_time = start_time + Duration::seconds(i as i64);
            // 写入字幕序号
            file.write(format!("{}\n", i + 1).as_bytes())?;
            // 写入时间轴
            file.write(format!("{} --> {}\n", Self::srt_timestr(i), Self::srt_timestr(i + 1)).as_bytes())?;
            // 写入时间文本以及字幕条目之间空一行
            file.write(format!("{}\n\n", current_start_time.format("%Y-%m-%d %H:%M:%S")).as_bytes())?;
        }
        Ok(())
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let seconds = FfmpegUtil::video_seconds("C:/Users/Administrator/Videos/20250806191023_20250806192914.mp4");
        let seconds = seconds.unwrap();
        println!("{:?}", seconds);
        let timestr = FfmpegUtil::srt_timestr(seconds);
        println!("{:?}", timestr);
        // 示例用法
        let _x = FfmpegUtil::generate_srt(
            "2023-10-01 12:00:00",
            seconds,  // 生成10秒的字幕
            "output.srt",
        ).unwrap();
    }
}