//! 美家卡智影 Tauri 后端入口
//!
//! 模块拆分：
//! - ffmpeg_cmd: FFmpeg 命令封装
//! - video_processing: 压制成片业务逻辑
//! - utils: 通用工具函数
//! - commands: Tauri IPC 命令入口（按领域拆分）
//! - storage: 本地存储引擎

mod ffmpeg_cmd;
mod video_processing;
mod utils;

mod commands;
mod storage;

use serde::{Deserialize, Serialize};
use tauri::Manager;

// ============================================================
// 配置常量
// ============================================================

// ============================================================
// 应用重启命令
// ============================================================

#[tauri::command]
fn restart_app(app: tauri::AppHandle) {
    app.restart();
}

#[tauri::command]
fn open_devtools(window: tauri::WebviewWindow) {
    let _ = window.open_devtools();
}

// ============================================================
// 公共类型导出
// ============================================================

#[derive(Serialize)]
pub struct ApiResponse<T> {
    pub code: i32,
    pub message: String,
    pub data: Option<T>,
}

// ============================================================
// 视频缓存清理
// ============================================================

/// 清理 video_cache 目录
///
/// 策略：
/// 1. 删除超过 30 天未修改的文件
/// 2. 总容量超过 500MB 时，按修改时间删最旧文件直到 300MB
fn clean_video_cache(app_data_dir: &std::path::Path) {
    let cache_dir = app_data_dir.join("video_cache");
    if !cache_dir.exists() {
        return;
    }

    let max_age = std::time::Duration::from_secs(30 * 24 * 60 * 60);
    let max_total_size: u64 = 2 * 1024 * 1024 * 1024;
    let target_size: u64 = 1024 * 1024 * 1024;
    let now = std::time::SystemTime::now();

    let mut entries: Vec<(std::path::PathBuf, std::time::SystemTime, u64)> = Vec::new();
    let mut total_size: u64 = 0;

    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("[cache] 无法读取缓存目录: {}", e);
            return;
        }
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if !metadata.is_file() { continue; }
        let mtime = metadata.modified().unwrap_or(now);
        let size = metadata.len();
        total_size += size;
        entries.push((entry.path(), mtime, size));
    }

    // 1. 删除超过 30 天的文件
    let mut deleted_size: u64 = 0;
    for (path, mtime, size) in &entries {
        if now.duration_since(*mtime).unwrap_or_default() > max_age {
            if std::fs::remove_file(path).is_ok() {
                deleted_size += size;
            }
        }
    }

    // 2. 容量超限，删最旧的
    let remaining_size = total_size.saturating_sub(deleted_size);
    if remaining_size > max_total_size {
        let mut sorted = entries;
        sorted.sort_by_key(|(_, mtime, _)| *mtime);
        let mut to_free = remaining_size.saturating_sub(target_size);
        for (path, _, size) in sorted {
            if to_free == 0 { break; }
            if path.exists() && std::fs::remove_file(&path).is_ok() {
                to_free = to_free.saturating_sub(size);
            }
        }
    }
}

/// 手动清理 video_cache 目录，返回释放的字节数
#[tauri::command]
fn clear_video_cache_cmd() -> Result<u64, String> {
    let app_data_dir = crate::storage::paths::get_app_data_dir()
        .map_err(|e| e.to_string())?;
    let cache_dir = app_data_dir.join("video_cache");
    if !cache_dir.exists() {
        return Ok(0);
    }

    let mut freed: u64 = 0;
    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => return Err(format!("无法读取缓存目录: {}", e)),
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if !metadata.is_file() { continue; }
        let size = metadata.len();
        if std::fs::remove_file(entry.path()).is_ok() {
            freed += size;
        }
    }

    Ok(freed)
}

/// 获取 video_cache 目录当前占用大小（字节）
#[tauri::command]
fn get_video_cache_size_cmd() -> Result<u64, String> {
    let app_data_dir = crate::storage::paths::get_app_data_dir()
        .map_err(|e| e.to_string())?;
    let cache_dir = app_data_dir.join("video_cache");
    if !cache_dir.exists() {
        return Ok(0);
    }

    let mut total: u64 = 0;
    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => return Err(format!("无法读取缓存目录: {}", e)),
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if metadata.is_file() {
            total += metadata.len();
        }
    }

    Ok(total)
}

// ============================================================
// BGM 缓存清理
// ============================================================

/// 清理 bgm_cache 目录
///
/// 策略：和视频缓存一致
/// 1. 删除超过 30 天未修改的文件
/// 2. 总容量超过 200MB 时，按修改时间删最旧文件直到 100MB
fn clean_bgm_cache(app_data_dir: &std::path::Path) {
    let cache_dir = app_data_dir.join("bgm_cache");
    if !cache_dir.exists() {
        return;
    }

    let max_age = std::time::Duration::from_secs(30 * 24 * 60 * 60);
    let max_total_size: u64 = 200 * 1024 * 1024;
    let target_size: u64 = 100 * 1024 * 1024;
    let now = std::time::SystemTime::now();

    let mut entries: Vec<(std::path::PathBuf, std::time::SystemTime, u64)> = Vec::new();
    let mut total_size: u64 = 0;

    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => {
            eprintln!("[bgm_cache] 无法读取缓存目录: {}", e);
            return;
        }
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if !metadata.is_file() { continue; }
        let mtime = metadata.modified().unwrap_or(now);
        let size = metadata.len();
        total_size += size;
        entries.push((entry.path(), mtime, size));
    }

    // 1. 删除超过 30 天的文件
    let mut deleted_size: u64 = 0;
    for (path, mtime, size) in &entries {
        if now.duration_since(*mtime).unwrap_or_default() > max_age {
            if std::fs::remove_file(path).is_ok() {
                deleted_size += size;
            }
        }
    }

    // 2. 容量超限，删最旧的
    let remaining_size = total_size.saturating_sub(deleted_size);
    if remaining_size > max_total_size {
        let mut sorted = entries;
        sorted.sort_by_key(|(_, mtime, _)| *mtime);
        let mut to_free = remaining_size.saturating_sub(target_size);
        for (path, _, size) in sorted {
            if to_free == 0 { break; }
            if path.exists() && std::fs::remove_file(&path).is_ok() {
                to_free = to_free.saturating_sub(size);
            }
        }
    }
}

/// 手动清理 bgm_cache 目录，返回释放的字节数
#[tauri::command]
fn clear_bgm_cache_cmd() -> Result<u64, String> {
    let app_data_dir = crate::storage::paths::get_app_data_dir()
        .map_err(|e| e.to_string())?;
    let cache_dir = app_data_dir.join("bgm_cache");
    if !cache_dir.exists() {
        return Ok(0);
    }

    let mut freed: u64 = 0;
    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => return Err(format!("无法读取缓存目录: {}", e)),
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if !metadata.is_file() { continue; }
        let size = metadata.len();
        if std::fs::remove_file(entry.path()).is_ok() {
            freed += size;
        }
    }

    Ok(freed)
}

/// 获取 bgm_cache 目录当前占用大小（字节）
#[tauri::command]
fn get_bgm_cache_size_cmd() -> Result<u64, String> {
    let app_data_dir = crate::storage::paths::get_app_data_dir()
        .map_err(|e| e.to_string())?;
    let cache_dir = app_data_dir.join("bgm_cache");
    if !cache_dir.exists() {
        return Ok(0);
    }

    let mut total: u64 = 0;
    let read_dir = match std::fs::read_dir(&cache_dir) {
        Ok(d) => d,
        Err(e) => return Err(format!("无法读取缓存目录: {}", e)),
    };

    for entry in read_dir.flatten() {
        let Ok(metadata) = entry.metadata() else { continue };
        if metadata.is_file() {
            total += metadata.len();
        }
    }

    Ok(total)
}

/// 获取 BGM 缓存目录路径（供前端下载 BGM 时使用）
#[tauri::command]
fn get_bgm_cache_dir() -> Result<String, String> {
    crate::storage::paths::get_bgm_cache_dir()
        .map(|p| p.to_string_lossy().to_string())
        .map_err(|e| e.to_string())
}

// ============================================================
// 应用入口
// ============================================================

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    // Windows: 禁用 D3D11 硬件视频解码，解决本地视频预览黑屏问题
    // Chromium 在 Windows 上的 D3D11 视频解码器与部分显卡驱动/视频编码不兼容，
    // 回退到软件解码可确保视频画面正常渲染。对 5~20 秒短视频预览性能影响可忽略。
    #[cfg(target_os = "windows")]
    {
        std::env::set_var(
            "WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS",
            "--disable-features=MediaFoundationD3D11VideoDecode"
        );
    }

    let _api_base_url = crate::storage::config::get_api_base_url_sync();

    tauri::Builder::default()
        .setup(|app| {
            // 初始化应用数据目录（所有业务数据的根目录）
            if let Ok(app_data_dir) = app.path().app_local_data_dir() {
                crate::storage::init_app_data_dir(app_data_dir.clone());
                // 后台清理过期视频缓存和 BGM 缓存，不阻塞首屏
                let app_data_dir_clone = app_data_dir.clone();
                std::thread::spawn(move || {
                    clean_video_cache(&app_data_dir_clone);
                    clean_bgm_cache(&app_data_dir_clone);
                });
            }

            // 窗口初始 visible=false，setup 阶段先显示窗口
            if let Some(window) = app.get_webview_window("main") {
                if let Err(e) = window.show() {
                    eprintln!("[setup] window.show() failed: {}", e);
                }
                if let Err(e) = window.set_focus() {
                    eprintln!("[setup] window.set_focus() failed: {}", e);
                }
            } else {
                eprintln!("[setup] main window not found");
            }

            // macOS 自定义菜单栏（中文本地化）
            #[cfg(target_os = "macos")]
            {
                use tauri::menu::{AboutMetadata, MenuBuilder, SubmenuBuilder};

                let app_menu = SubmenuBuilder::new(app, "美家卡智影")
                    .about_with_text(
                        "关于 美家卡智影",
                        Some(AboutMetadata {
                            name: Some("美家卡智影".to_string()),
                            ..Default::default()
                        }),
                    )
                    .separator()
                    .quit_with_text("退出")
                    .build()?;

                let edit_menu = SubmenuBuilder::new(app, "编辑")
                    .undo_with_text("撤销")
                    .redo_with_text("重做")
                    .separator()
                    .cut_with_text("剪切")
                    .copy_with_text("复制")
                    .paste_with_text("粘贴")
                    .separator()
                    .select_all_with_text("全选")
                    .build()?;

                let window_menu = SubmenuBuilder::new(app, "窗口")
                    .minimize_with_text("最小化")
                    .maximize_with_text("最大化")
                    .separator()
                    .close_window_with_text("关闭窗口")
                    .build()?;

                let menu = MenuBuilder::new(app)
                    .item(&app_menu)
                    .item(&edit_menu)
                    .item(&window_menu)
                    .build()?;

                app.set_menu(menu)?;
            }

            Ok(())
        })
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(tauri_plugin_process::init())
        .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
            // 单实例：第二个实例启动时，将已有窗口带到前台
            if let Some(window) = app.get_webview_window("main") {
                let _ = window.unminimize();
                let _ = window.set_focus();
            }
        }))
        .invoke_handler(tauri::generate_handler![
            storage::config::load_app_config,
            storage::config::save_app_config,
            storage::config::get_environment_presets,
            restart_app,
            open_devtools,
            // 项目存储
            commands::project::save_project_meta_raw,
            commands::project::load_project_meta,
            commands::project::save_project_segments_raw,
            commands::project::load_project_segments,
            commands::project::list_local_projects,
            commands::project::delete_local_project,
            commands::project::delete_project_file,

            commands::auth_state::load_auth_state,
            commands::auth_state::save_auth_state,
            commands::auth_state::clear_auth_state,
            commands::file::validate_media_path,

            // 字幕烧录
            burn_subtitle,

            // 保存项目资源（封面图片等）
            commands::asset::save_project_asset,
            // 获取视频保存路径
            commands::asset::get_video_save_path,
            // 压制成片
            video_composite_synthesis,
            // 获取成品保存路径
            commands::product::get_product_save_path,
            // 列出本地成品
            commands::product::list_local_products,
            // 删除本地成品
            commands::product::delete_local_product,
            // 重命名成品
            commands::product::rename_local_product,
            // 导出成品
            commands::product::export_product,
            // 音频管理
            commands::voice::save_audio,
            commands::voice::extract_audio_segment,
            commands::voice::upload_audio_file,
            // 音色素材库
            commands::voice::load_voice_materials,
            commands::voice::save_voice_material,
            commands::voice::delete_voice_material_cmd,
            // 封面形象库
            commands::cover_avatar::load_cover_avatars,
            commands::cover_avatar::save_cover_avatar,
            commands::cover_avatar::delete_cover_avatar_cmd,
            commands::cover_avatar::save_cover_avatar_image,
            // 压制成片（Phase 2）
            commands::video_compose::extract_video_segment,
            commands::video_compose::concat_video_clips,
            commands::video_compose::generate_empty_shot_clip,
            commands::video_compose::upload_video_file,
            commands::video_compose::download_file,
            commands::video_compose::mix_bgm_to_video,
            // 视频元数据读取（ffprobe）
            get_video_metadata_cmd,
            // 视频预览转码（统一浏览器兼容格式）
            transcode_for_preview_cmd,
            // 缓存清理
            get_video_cache_size_cmd,
            clear_video_cache_cmd,
            get_bgm_cache_size_cmd,
            clear_bgm_cache_cmd,
            get_bgm_cache_dir,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

// ============================================================
// 字幕烧录命令
// ============================================================

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct BurnSubtitleRequest {
    video_path: String,
    ass_path: String,
    output_path: String,
    overlay_image: Option<String>,
}

#[tauri::command]
async fn burn_subtitle(
    app: tauri::AppHandle,
    request: BurnSubtitleRequest,
) -> ApiResponse<String> {
    match ffmpeg_cmd::burn_ass_subtitle(
        &app,
        &request.video_path,
        &request.ass_path,
        &request.output_path,
        request.overlay_image.as_deref(),
    ).await {
        Ok(_) => ApiResponse {
            code: 200,
            message: "Subtitle burned successfully".to_string(),
            data: Some(request.output_path),
        },
        Err(e) => ApiResponse {
            code: 500,
            message: format!("Failed to burn subtitle: {}", e),
            data: None,
        },
    }
}

// ============================================================
// 压制成片命令
// ============================================================

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct VideoCompositeRequest {
    video_paths: Vec<String>,
    audio_path: Option<String>,
    cover_path: Option<String>,
    output_path: String,
}

#[tauri::command]
async fn video_composite_synthesis(
    app: tauri::AppHandle,
    request: VideoCompositeRequest,
) -> ApiResponse<String> {
    // 从 output_path 解析项目目录（格式：.../projects/{project_id}/products/{filename}）
    let output_path = std::path::PathBuf::from(&request.output_path);
    let project_dir = match output_path.parent().and_then(|p| p.parent()) {
        Some(dir) => dir.to_path_buf(),
        None => {
            return ApiResponse {
                code: 500,
                message: "无法从输出路径解析项目目录".to_string(),
                data: None,
            };
        }
    };
    let payload = serde_json::json!({
        "videoPaths": request.video_paths,
        "audioPath": request.audio_path.unwrap_or_default(),
        "coverPath": request.cover_path,
        "outputPath": request.output_path,
    });
    let response = video_processing::handle_video_synthesis(&app, &project_dir, payload).await;
    if response.code == 200 {
        if let Some(data) = response.data {
            if let Some(path) = data.as_object()
                .and_then(|obj| obj.get("outputPath"))
                .and_then(|v| v.as_str())
            {
                return ApiResponse {
                    code: 200,
                    message: "压制成片成功".to_string(),
                    data: Some(path.to_string()),
                };
            }
        }
    }
    ApiResponse {
        code: response.code,
        message: response.message,
        data: None,
    }
}

// ============================================================
// 视频元数据读取（ffprobe）
// ============================================================

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct GetVideoMetadataRequest {
    path: String,
}

#[tauri::command]
async fn get_video_metadata_cmd(
    app: tauri::AppHandle,
    request: GetVideoMetadataRequest,
) -> ApiResponse<ffmpeg_cmd::VideoMetadata> {
    match ffmpeg_cmd::get_video_metadata(&app, &request.path).await {
        Ok(meta) => ApiResponse {
            code: 200,
            message: "读取成功".to_string(),
            data: Some(meta),
        },
        Err(e) => ApiResponse {
            code: 500,
            message: e,
            data: None,
        },
    }
}

// ============================================================
// 视频预览转码
// ============================================================

#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TranscodeForPreviewRequest {
    path: String,
}

#[tauri::command]
async fn transcode_for_preview_cmd(
    app: tauri::AppHandle,
    request: TranscodeForPreviewRequest,
) -> ApiResponse<String> {
    match ffmpeg_cmd::transcode_for_preview(&app, &request.path).await {
        Ok(path) => ApiResponse {
            code: 200,
            message: "转码成功".to_string(),
            data: Some(path),
        },
        Err(e) => ApiResponse {
            code: 500,
            message: e,
            data: None,
        },
    }
}

// ============================================================
// 错误处理扩展
// ============================================================

/// 将 `Result<T, E>` 的错误转换为 `String`
///
/// 业界主流做法：通过 trait extension 避免 `.map_err(|e| e.to_string())`
/// 闭包参数类型推断失败的问题。
pub trait StringResultExt<T> {
    fn map_err_string(self) -> Result<T, String>;
}

impl<T, E: std::fmt::Display> StringResultExt<T> for Result<T, E> {
    fn map_err_string(self) -> Result<T, String> {
        self.map_err(|e| e.to_string())
    }
}
