//! 下载中心 API — 任务列表、下载链接、文件下载、过期清理

use axum::body::Body;
use axum::extract::{Path, Query, State};
use axum::http::header;
use axum::response::Response;
use axum::Json;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};

use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};

/// 下载任务列表查询参数
#[derive(Debug, Deserialize)]
pub struct DownloadListQuery {
    #[serde(default = "default_page")]
    pub page: i64,
    #[serde(default = "default_page_size")]
    pub page_size: i64,
}

/// 清理请求参数
#[derive(Debug, Deserialize)]
pub struct CleanupRequest {
    /// 保留天数，默认7天
    #[serde(default = "default_retain_days")]
    pub retain_days: i32,
}

/// 下载任务列表项
#[derive(Debug, Serialize)]
pub struct DownloadTaskItem {
    pub id: i64,
    pub task_type: String,
    pub status: i8,
    pub status_text: String,
    pub file_name: Option<String>,
    pub progress: i32,
    pub error_message: Option<String>,
    pub created_at: Option<String>,
    pub completed_at: Option<String>,
}

fn default_page() -> i64 { 1 }
fn default_page_size() -> i64 { 20 }
fn default_retain_days() -> i32 { 7 }

/// 状态码转文本
fn download_status_text(status: i8) -> &'static str {
    match status {
        0 => "排队中",
        1 => "处理中",
        2 => "已完成",
        3 => "失败",
        _ => "未知",
    }
}

/// 数据库行映射
#[derive(Debug, sqlx::FromRow)]
struct DownloadTaskRow {
    id: i64,
    task_type: String,
    status: i8,
    file_name: Option<String>,
    file_path: Option<String>,
    progress: i32,
    error_message: Option<String>,
    created_at: Option<NaiveDateTime>,
    completed_at: Option<NaiveDateTime>,
}

/// 将数据库行转为响应对象
fn row_to_item(row: DownloadTaskRow) -> DownloadTaskItem {
    DownloadTaskItem {
        id: row.id,
        task_type: row.task_type,
        status: row.status,
        status_text: download_status_text(row.status).to_string(),
        file_name: row.file_name,
        progress: row.progress,
        error_message: if row.status == 3 { row.error_message } else { None },
        created_at: row.created_at.map(|t| t.to_string()),
        completed_at: row.completed_at.map(|t| t.to_string()),
    }
}

/// GET /api/downloads — 下载任务列表
pub async fn list_downloads(
    user: CurrentUser,
    State(state): State<super::AppState>,
    Query(params): Query<DownloadListQuery>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "charge_record:export")?;
    let user_id = user.user_id;
    let offset = (params.page - 1) * params.page_size;

    // 查询总数
    let total: i64 = sqlx::query_scalar(
        "SELECT COUNT(*) FROM download_tasks WHERE user_id = ?"
    )
    .bind(user_id)
    .fetch_one(&state.mysql)
    .await?;

    // 查询列表
    let rows = sqlx::query_as::<_, DownloadTaskRow>(
        "SELECT id, task_type, status, file_name, file_path, progress, \
         error_message, created_at, completed_at \
         FROM download_tasks WHERE user_id = ? \
         ORDER BY created_at DESC LIMIT ? OFFSET ?"
    )
    .bind(user_id)
    .bind(params.page_size)
    .bind(offset)
    .fetch_all(&state.mysql)
    .await?;

    let list: Vec<DownloadTaskItem> = rows.into_iter().map(row_to_item).collect();

    Ok(Json(json!({
        "code": 0,
        "data": {
            "list": list,
            "total": total,
            "page": params.page,
            "page_size": params.page_size,
        },
        "message": "ok"
    })))
}

/// GET /api/downloads/:id — 获取下载链接
pub async fn get_download(
    user: CurrentUser,
    State(state): State<super::AppState>,
    Path(id): Path<i64>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "charge_record:export")?;
    let row: Option<DownloadTaskRow> = sqlx::query_as(
        "SELECT id, task_type, status, file_name, file_path, progress, \
         error_message, created_at, completed_at \
         FROM download_tasks WHERE id = ?"
    )
    .bind(id)
    .fetch_optional(&state.mysql)
    .await?;

    let row = row.ok_or_else(|| AppError::NotFound("下载任务不存在".into()))?;

    if row.status != 2 {
        return Err(AppError::BadRequest("任务尚未完成".into()));
    }

    let file_path = row.file_path.unwrap_or_default();

    // 检查文件是否存在
    if !std::path::Path::new(&file_path).exists() {
        return Err(AppError::NotFound("文件不存在或已清理".into()));
    }

    // 返回文件下载路径（后续可改为签名URL）
    let download_url = format!("/api/downloads/{}/file", id);

    Ok(Json(json!({
        "code": 0,
        "data": {
            "id": row.id,
            "file_name": row.file_name,
            "download_url": download_url,
        },
        "message": "ok"
    })))
}

/// GET /api/downloads/:id/file — 下载文件（二进制流）
pub async fn download_file(
    user: CurrentUser,
    State(state): State<super::AppState>,
    Path(id): Path<i64>,
) -> Result<Response<Body>, AppError> {
    auth::check_permission(&user, "charge_record:export")?;

    let db = &state.mysql;

    // 查询下载任务（限定本人）
    let task: Option<DownloadTaskRow> = sqlx::query_as(
        "SELECT id, task_type, status, file_name, file_path, progress, \
         error_message, created_at, completed_at \
         FROM download_tasks WHERE id = ? AND user_id = ?",
    )
    .bind(id)
    .bind(user.user_id)
    .fetch_optional(db)
    .await?;

    let task = task.ok_or_else(|| AppError::NotFound("下载任务不存在".into()))?;

    if task.status != 2 {
        return Err(AppError::BadRequest("文件未生成完成".into()));
    }

    let file_path = task
        .file_path
        .ok_or_else(|| AppError::NotFound("文件路径不存在".into()))?;

    // 读取文件内容
    let file_content = tokio::fs::read(&file_path).await.map_err(|e| {
        tracing::error!("读取下载文件失败 path={} err={}", file_path, e);
        AppError::Internal(format!("读取文件失败: {}", e))
    })?;

    // 推断文件名
    let file_name = task.file_name.unwrap_or_else(|| "download.xlsx".to_string());

    // 构建文件下载响应
    let response = Response::builder()
        .status(200)
        .header(
            header::CONTENT_TYPE,
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        )
        .header(
            header::CONTENT_DISPOSITION,
            format!("attachment; filename=\"{}\"", file_name),
        )
        .body(Body::from(file_content))
        .map_err(|e| AppError::Internal(format!("构建响应失败: {}", e)))?;

    Ok(response)
}

/// POST /api/downloads/cleanup — 清理过期文件
pub async fn cleanup_downloads(
    user: CurrentUser,
    State(state): State<super::AppState>,
    Json(req): Json<CleanupRequest>,
) -> Result<Json<Value>, AppError> {
    auth::check_permission(&user, "charge_record:export")?;
    // 查询过期任务（状态为完成且超过保留天数）
    let expired: Vec<DownloadTaskRow> = sqlx::query_as(
        "SELECT id, task_type, status, file_name, file_path, progress, \
         error_message, created_at, completed_at \
         FROM download_tasks \
         WHERE status = 2 AND completed_at < DATE_SUB(NOW(), INTERVAL ? DAY)"
    )
    .bind(req.retain_days)
    .fetch_all(&state.mysql)
    .await?;

    let mut cleaned = 0u32;

    for task in &expired {
        // 删除物理文件
        if let Some(ref path) = task.file_path {
            let _ = tokio::fs::remove_file(path).await;
        }

        // 更新数据库记录
        let _ = sqlx::query(
            "UPDATE download_tasks SET file_path = NULL, status = 3, \
             error_message = '文件已过期清理' WHERE id = ?"
        )
        .bind(task.id)
        .execute(&state.mysql)
        .await;

        cleaned += 1;
    }

    tracing::info!("清理过期下载文件 {} 个", cleaned);

    Ok(Json(json!({
        "code": 0,
        "data": { "cleaned": cleaned },
        "message": format!("已清理 {} 个过期文件", cleaned)
    })))
}
