//! 视频合成命令
//!
//! 提供本地 FFmpeg 视频裁剪、拼接、上传功能。
//! 用于 Phase 2 视频生成流程：裁剪素材 → 拼接 → 上传七牛云 → Vidu 对口型。

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

use crate::ffmpeg_cmd;
use crate::ffmpeg_cmd::sanitize_output_path;
use crate::ApiResponse;

/// 视频合成响应
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ComposeVideoResult {
    pub output_path: String,
    pub duration: f64,
}

/// 通用上传响应
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadResult {
    pub url: String,
}

/// 兼容旧命名
pub type UploadVideoResult = UploadResult;

/// 获取项目视频目录
fn get_project_video_dir(project_id: &str) -> Result<std::path::PathBuf, String> {
    crate::storage::get_project_videos_dir(project_id)
        .map_err(|e| format!("获取项目视频目录失败: {}", e))
}


/// 直接拼接已标准化的视频片段（不做裁剪，只做 concat）
#[tauri::command]
pub async fn concat_video_clips(
    app: AppHandle,
    project_id: String,
    clip_paths: Vec<String>,
) -> ApiResponse<ComposeVideoResult> {
    let video_dir = match get_project_video_dir(&project_id) {
        Ok(dir) => dir,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: e,
                data: None,
            };
        }
    };

    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_millis();

    // 生成 concat 列表文件
    let list_path = video_dir.join(format!("concat_list_{}.txt", timestamp));
    let mut list_content = String::new();
    for path in &clip_paths {
        list_content.push_str(&format!("file '{}'\n", ffmpeg_cmd::escape_ffmpeg_path(path)));
    }
    if let Err(e) = std::fs::write(&list_path, list_content) {
        return ApiResponse {
            code: 500,
            message: format!("创建拼接列表失败: {}", e),
            data: None,
        };
    }

    // 执行拼接（所有片段已标准化，编码一致，直接用 copy）
    let output_filename = format!("composed_{}.mp4", timestamp);
    let output_path = video_dir.join(&output_filename);
    let output_path_str = output_path.to_string_lossy().to_string();

    let concat_res = ffmpeg_cmd::concat_videos_copy(&app, list_path.to_str().unwrap(), &output_path_str).await;

    // 清理列表文件
    let _ = std::fs::remove_file(&list_path);

    if let Err(e) = concat_res {
        return ApiResponse {
            code: 500,
            message: format!("拼接视频失败: {}", e),
            data: None,
        };
    }

    // 总时长由前端提供（各片段时长的精确累加）
    // 此处不通过 ffprobe 读取，避免依赖
    let total_duration = 0.0;

    ApiResponse {
        code: 200,
        message: "视频拼接成功".to_string(),
        data: Some(ComposeVideoResult {
            output_path: output_path_str,
            duration: total_duration,
        }),
    }
}

/// 空镜片段生成请求参数
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GenerateEmptyShotClipArgs {
    pub video_source: String,
    pub audio_source: String,
    pub audio_start: f64,
    pub duration: f64,
    pub output_path: String,
}

/// 生成空镜片段（原子操作：截取视频 → 截取音频 → 替换音频 → 清理临时文件）
#[tauri::command]
pub async fn generate_empty_shot_clip(
    app: AppHandle,
    args: GenerateEmptyShotClipArgs,
) -> ApiResponse<String> {
    let safe_output = match sanitize_output_path(&args.output_path) {
        Ok(p) => p,
        Err(e) => return ApiResponse { code: 500, message: e, data: None },
    };

    let temp_video = format!("{}.tmp_video.mp4", safe_output);
    let temp_audio = format!("{}.tmp_audio.mp3", safe_output);

    let cleanup = || {
        let _ = std::fs::remove_file(&temp_video);
        let _ = std::fs::remove_file(&temp_audio);
    };

    // 1. 截取空镜视频并标准化
    if let Err(e) = ffmpeg_cmd::clip_video(&app, &args.video_source, 0.0, args.duration, &temp_video).await {
        cleanup();
        return ApiResponse {
            code: 500,
            message: format!("截取空镜视频失败: {}", e),
            data: None,
        };
    }

    // 2. 截取音频
    if let Err(e) = ffmpeg_cmd::extract_audio_segment(&app, &args.audio_source, args.audio_start, args.duration, &temp_audio).await {
        cleanup();
        return ApiResponse {
            code: 500,
            message: format!("截取音频失败: {}", e),
            data: None,
        };
    }

    // 3. 替换音频到视频
    if let Err(e) = ffmpeg_cmd::replace_audio_track(&app, &temp_video, &temp_audio, &safe_output).await {
        cleanup();
        return ApiResponse {
            code: 500,
            message: format!("替换音频失败: {}", e),
            data: None,
        };
    }

    // 4. 清理临时文件
    cleanup();

    ApiResponse {
        code: 200,
        message: "空镜片段生成成功".to_string(),
        data: Some(safe_output),
    }
}

/// 混合 BGM 请求参数
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MixBgmToVideoArgs {
    pub video_path: String,
    pub bgm_path: String,
    pub output_path: String,
    pub video_volume: Option<f64>,
    pub bgm_volume: Option<f64>,
}

/// 混合背景音乐到视频（保留原音频）
#[tauri::command]
pub async fn mix_bgm_to_video(
    app: AppHandle,
    args: MixBgmToVideoArgs,
) -> ApiResponse<String> {
    let safe_output = match sanitize_output_path(&args.output_path) {
        Ok(p) => p,
        Err(e) => return ApiResponse { code: 500, message: e, data: None },
    };

    let video_vol = args.video_volume.unwrap_or(1.0);
    let bgm_vol = args.bgm_volume.unwrap_or(0.25);

    // 如果输出路径和输入路径相同，使用临时文件避免 FFmpeg 报错
    let use_temp = args.video_path == safe_output;
    let temp_output = if use_temp {
        format!("{}.tmp_bgm.mp4", safe_output)
    } else {
        safe_output.clone()
    };

    match crate::ffmpeg_cmd::mix_bgm_to_video(
        &app,
        &args.video_path,
        &args.bgm_path,
        &temp_output,
        video_vol,
        bgm_vol,
    ).await {
        Ok(_) => {
            if use_temp {
                // 用混合后的文件替换原文件
                let _ = std::fs::remove_file(&safe_output);
                if let Err(e) = std::fs::rename(&temp_output, &safe_output) {
                    let _ = std::fs::remove_file(&temp_output);
                    return ApiResponse {
                        code: 500,
                        message: format!("替换原文件失败: {}", e),
                        data: None,
                    };
                }
            }
            ApiResponse {
                code: 200,
                message: "BGM 混合成功".to_string(),
                data: Some(safe_output),
            }
        }
        Err(e) => {
            let _ = std::fs::remove_file(&temp_output);
            ApiResponse {
                code: 500,
                message: format!("BGM 混合失败: {}", e),
                data: None,
            }
        }
    }
}

/// 截取视频片段请求参数
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExtractVideoSegmentArgs {
    pub input_path: String,
    pub start: f64,
    pub duration: f64,
    pub output_path: String,
}

/// 截取视频片段（FFmpeg clip_video 封装）
#[tauri::command]
pub async fn extract_video_segment(
    app: AppHandle,
    args: ExtractVideoSegmentArgs,
) -> ApiResponse<String> {
    let safe_output = match sanitize_output_path(&args.output_path) {
        Ok(p) => p,
        Err(e) => return ApiResponse { code: 500, message: e, data: None },
    };

    let safe_input = if args.input_path.starts_with("http://") || args.input_path.starts_with("https://") {
        args.input_path.clone()
    } else if std::path::Path::new(&args.input_path).exists() {
        args.input_path.clone()
    } else {
        return ApiResponse {
            code: 500,
            message: format!("输入文件不存在: {}", args.input_path),
            data: None,
        };
    };

    match ffmpeg_cmd::clip_video(&app, &safe_input, args.start, args.duration, &safe_output).await {
        Ok(_) => ApiResponse {
            code: 200,
            message: "视频片段截取成功".to_string(),
            data: Some(safe_output),
        },
        Err(e) => ApiResponse {
            code: 500,
            message: format!("截取视频片段失败: {}", e),
            data: None,
        },
    }
}

/// 通用文件上传（本地 → 后端 → 七牛云）
pub async fn upload_file_to_backend(
    app: &tauri::AppHandle,
    local_path: &str,
    endpoint: &str,
    default_filename: &str,
    mime_type: &str,
    read_error_prefix: &str,
) -> ApiResponse<UploadResult> {
    // 文件大小限制（字节）
    let max_size: u64 = match endpoint {
        "/upload/video" => 500 * 1024 * 1024,  // 500MB
        "/upload/audio" => 100 * 1024 * 1024,  // 100MB
        _ => 100 * 1024 * 1024,                // 默认 100MB
    };

    let file_bytes = match std::fs::read(local_path) {
        Ok(bytes) => bytes,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("{}: {}", read_error_prefix, e),
                data: None,
            };
        }
    };

    let file_size = file_bytes.len() as u64;
    if file_size > max_size {
        return ApiResponse {
            code: 413,
            message: format!(
                "文件大小 {}MB 超过限制 {}MB",
                file_size / 1024 / 1024,
                max_size / 1024 / 1024
            ),
            data: None,
        };
    }

    let filename = std::path::Path::new(local_path)
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or(default_filename)
        .to_string();

    let backend_url = crate::storage::config::get_api_base_url_sync();
    let upload_url = format!("{}{}", backend_url, endpoint);

    // 读取本地认证 token
    let auth_token: Option<String> = match crate::storage::auth::load_auth_state::<serde_json::Value>(app) {
        Ok(Some(state)) => state.get("accessToken").and_then(|v| v.as_str()).map(|s| s.to_string()),
        _ => None,
    };

    let client = reqwest::Client::new();

    let form = reqwest::multipart::Form::new()
        .part(
            "file",
            reqwest::multipart::Part::bytes(file_bytes)
                .file_name(filename)
                .mime_str(mime_type)
                .unwrap_or_else(|_| reqwest::multipart::Part::bytes(vec![])),
        );

    let mut request = client.post(&upload_url).multipart(form);
    if let Some(token) = auth_token {
        request = request.header("Authorization", format!("Bearer {}", token));
    }

    let response = match request.send().await {
        Ok(resp) => resp,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("上传请求失败: {}", e),
                data: None,
            };
        }
    };

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response.text().await.unwrap_or_default();
        return ApiResponse {
            code: status.as_u16() as i32,
            message: format!("上传失败: {} - {}", status, error_text),
            data: None,
        };
    }

    let result: serde_json::Value = match response.json().await {
        Ok(data) => data,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("解析上传响应失败: {}", e),
                data: None,
            };
        }
    };

    let url = result
        .get("data")
        .and_then(|d| d.get("url"))
        .and_then(|u| u.as_str())
        .map(|s| s.to_string());

    match url {
        Some(url) => ApiResponse {
            code: 200,
            message: "上传成功".to_string(),
            data: Some(UploadResult { url }),
        },
        None => ApiResponse {
            code: 500,
            message: "上传响应中未找到 URL".to_string(),
            data: None,
        },
    }
}

/// 上传本地视频到后端，后端上传到七牛云并返回 URL
#[tauri::command]
pub async fn upload_video_file(
    app: tauri::AppHandle,
    local_path: String,
) -> ApiResponse<UploadVideoResult> {
    upload_file_to_backend(
        &app,
        &local_path,
        "/upload/video",
        "video.mp4",
        "video/mp4",
        "读取视频文件失败",
    ).await
}

/// 从 URL 下载文件到本地
#[tauri::command]
pub async fn download_file(
    url: String,
    output_path: String,
) -> ApiResponse<String> {
    let safe_output = match sanitize_output_path(&output_path) {
        Ok(p) => p,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("路径验证失败: {}", e),
                data: None,
            };
        }
    };

    // 自动创建父目录
    if let Some(parent) = std::path::Path::new(&safe_output).parent() {
        if let Err(e) = std::fs::create_dir_all(parent) {
            return ApiResponse {
                code: 500,
                message: format!("创建目录失败: {}", e),
                data: None,
            };
        }
    }

    let client = reqwest::Client::new();
    let response = match client.get(&url).send().await {
        Ok(resp) => resp,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("下载请求失败: {}", e),
                data: None,
            };
        }
    };

    if !response.status().is_success() {
        return ApiResponse {
            code: response.status().as_u16() as i32,
            message: format!("下载失败: HTTP {}", response.status()),
            data: None,
        };
    }

    let bytes = match response.bytes().await {
        Ok(b) => b,
        Err(e) => {
            return ApiResponse {
                code: 500,
                message: format!("读取下载内容失败: {}", e),
                data: None,
            };
        }
    };

    if let Err(e) = std::fs::write(&safe_output, &bytes) {
        return ApiResponse {
            code: 500,
            message: format!("保存文件失败: {}", e),
            data: None,
        };
    }

    ApiResponse {
        code: 200,
        message: "下载成功".to_string(),
        data: Some(safe_output),
    }
}
