//! axum 处理器：上传 / 列表 / 删除 / 分析 / 进度 / 帧元数据 / 缩略图 / 大图。
//! 所有请求靠 `X-Client-Id` 头按浏览器隔离。

use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

use axum::extract::{Multipart, Path as AxPath, Query, State};
use axum::http::{header, HeaderMap, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use tokio::io::AsyncWriteExt;

use crate::core;

const ALLOWED_EXT: &[&str] = &["mp4", "mov", "mkv", "webm", "avi", "m4v", "flv", "wmv"];
const UPLOAD_CHUNK_HINT: usize = 1 << 20;

#[derive(Clone)]
pub struct AppState {
    pub root: PathBuf,
    pub jobs: core::JobMap,
}

impl AppState {
    pub fn new(root: PathBuf) -> Self {
        AppState {
            root,
            jobs: Arc::new(Mutex::new(HashMap::new())),
        }
    }
}

type ApiErr = (StatusCode, String);

fn err(code: StatusCode, msg: impl Into<String>) -> ApiErr {
    (code, msg.into())
}

fn client_id(headers: &HeaderMap) -> Result<String, ApiErr> {
    headers
        .get("x-client-id")
        .and_then(|v| v.to_str().ok())
        .filter(|s| !s.is_empty())
        .map(|s| s.to_string())
        .ok_or_else(|| err(StatusCode::BAD_REQUEST, "缺少 X-Client-Id"))
}

/// `<img src>` 没法带自定义头，图片接口允许用 `?c=<clientId>` query 传 client，
/// 兜底再看 header。
#[derive(Deserialize)]
pub struct ClientQuery {
    c: Option<String>,
}

fn client_from(q: &ClientQuery, headers: &HeaderMap) -> Result<String, ApiErr> {
    if let Some(c) = q.c.as_ref().filter(|s| !s.is_empty()) {
        return Ok(c.clone());
    }
    client_id(headers)
}

// ---------------------------------------------------------------------------
// 上传
// ---------------------------------------------------------------------------

pub async fn upload_video(
    State(st): State<AppState>,
    headers: HeaderMap,
    mut mp: Multipart,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;

    while let Some(mut field) = mp
        .next_field()
        .await
        .map_err(|e| err(StatusCode::BAD_REQUEST, format!("multipart 解析失败: {e}")))?
    {
        if field.name() != Some("file") {
            continue;
        }
        let filename = field.file_name().unwrap_or("video").to_string();
        let ext = Path::new(&filename)
            .extension()
            .and_then(|e| e.to_str())
            .map(|e| e.to_lowercase())
            .unwrap_or_default();
        if !ALLOWED_EXT.contains(&ext.as_str()) {
            return Err(err(
                StatusCode::BAD_REQUEST,
                format!("不支持的格式 .{ext}，支持: {}", ALLOWED_EXT.join(", ")),
            ));
        }

        let vid: String = uuid::Uuid::new_v4().simple().to_string().chars().take(12).collect();
        let vdir = core::video_dir(&st.root, &client, &vid);
        std::fs::create_dir_all(&vdir).map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
        let dst = vdir.join(format!("source.{ext}"));

        let mut size: u64 = 0;
        let write_res = async {
            let mut f = tokio::fs::File::create(&dst).await?;
            let mut buf: Vec<u8> = Vec::with_capacity(UPLOAD_CHUNK_HINT);
            while let Some(chunk) = field
                .chunk()
                .await
                .map_err(|e| std::io::Error::other(e.to_string()))?
            {
                size += chunk.len() as u64;
                buf.extend_from_slice(&chunk);
                if buf.len() >= UPLOAD_CHUNK_HINT {
                    f.write_all(&buf).await?;
                    buf.clear();
                }
            }
            if !buf.is_empty() {
                f.write_all(&buf).await?;
            }
            f.flush().await?;
            Ok::<(), std::io::Error>(())
        }
        .await;

        if let Err(e) = write_res {
            let _ = std::fs::remove_dir_all(&vdir);
            return Err(err(StatusCode::INTERNAL_SERVER_ERROR, format!("写入失败: {e}")));
        }

        let duration = core::probe_duration(&dst);
        if duration <= 0.0 {
            let _ = std::fs::remove_dir_all(&vdir);
            return Err(err(StatusCode::BAD_REQUEST, "无法解析视频（可能不是有效视频文件）"));
        }

        core::write_meta(
            &st.root,
            &client,
            &vid,
            &core::Meta {
                name: filename.clone(),
                size,
                duration,
                interval: None,
                status: "uploaded".into(),
                frames: vec![],
            },
        )
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

        return Ok(Json(json!({
            "video_id": vid, "name": filename, "size": size, "duration": duration
        })));
    }

    Err(err(StatusCode::BAD_REQUEST, "未收到文件"))
}

// ---------------------------------------------------------------------------
// 列表 / 删除
// ---------------------------------------------------------------------------

pub async fn list_videos(
    State(st): State<AppState>,
    headers: HeaderMap,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;
    let vids = core::list_videos(&st.root, &client);
    Ok(Json(serde_json::to_value(vids).unwrap_or(json!([]))))
}

pub async fn delete_video(
    State(st): State<AppState>,
    AxPath(vid): AxPath<String>,
    headers: HeaderMap,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;
    if core::delete_video(&st.root, &client, &vid) {
        if let Ok(mut m) = st.jobs.lock() {
            m.remove(&format!("{}/{}", core::safe_id(&client), core::safe_id(&vid)));
        }
        Ok(Json(json!({"ok": true})))
    } else {
        Err(err(StatusCode::NOT_FOUND, "不存在"))
    }
}

// ---------------------------------------------------------------------------
// 分析 / 进度 / 帧元数据
// ---------------------------------------------------------------------------

#[derive(Deserialize)]
pub struct AnalyzeReq {
    interval: Option<f64>,
}

pub async fn analyze(
    State(st): State<AppState>,
    AxPath(vid): AxPath<String>,
    headers: HeaderMap,
    Json(req): Json<AnalyzeReq>,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;
    let interval = req.interval.unwrap_or(8.0);
    if !(0.2..=600.0).contains(&interval) {
        return Err(err(StatusCode::BAD_REQUEST, "interval 需在 0.2 ~ 600 秒之间"));
    }
    core::start_analysis(st.root.clone(), st.jobs.clone(), client, vid, interval).map_err(|e| {
        if e.contains("不存在") {
            err(StatusCode::NOT_FOUND, e)
        } else {
            err(StatusCode::INTERNAL_SERVER_ERROR, e)
        }
    })?;
    Ok(Json(json!({"ok": true, "interval": interval})))
}

pub async fn job_status(
    State(st): State<AppState>,
    AxPath(vid): AxPath<String>,
    headers: HeaderMap,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;
    if let Some(j) = core::get_job(&st.jobs, &client, &vid) {
        return Ok(Json(json!({
            "status": j.status,
            "progress": (j.progress * 10000.0).round() / 10000.0,
            "message": j.message,
            "frame_count": j.frame_count,
            "error": j.error,
        })));
    }
    // 没有活动任务：回退看 meta
    if let Some(m) = core::read_meta(&st.root, &client, &vid) {
        if m.status == "done" {
            return Ok(Json(json!({
                "status": "done", "progress": 1.0,
                "message": "已完成", "frame_count": m.frames.len(),
            })));
        }
    }
    Err(err(StatusCode::NOT_FOUND, "无任务"))
}

pub async fn frames(
    State(st): State<AppState>,
    AxPath(vid): AxPath<String>,
    headers: HeaderMap,
) -> Result<Json<Value>, ApiErr> {
    let client = client_id(&headers)?;
    let m = core::read_meta(&st.root, &client, &vid).ok_or_else(|| err(StatusCode::NOT_FOUND, "不存在"))?;
    Ok(Json(json!({
        "video_id": vid,
        "name": m.name,
        "interval": m.interval,
        "duration": m.duration,
        "status": m.status,
        "frames": serde_json::to_value(&m.frames).unwrap_or(json!([])),
    })))
}

// ---------------------------------------------------------------------------
// 图片：缩略图 / 大图
// ---------------------------------------------------------------------------

async fn serve_img(
    st: &AppState,
    client: &str,
    vid: &str,
    sub: &str,
    idx: u32,
) -> Result<Response, ApiErr> {
    let p = core::video_dir(&st.root, client, vid).join(sub).join(format!("{idx:06}.jpg"));
    let bytes = tokio::fs::read(&p).await.map_err(|_| err(StatusCode::NOT_FOUND, "帧不存在"))?;
    Ok((
        [
            (header::CONTENT_TYPE, "image/jpeg"),
            (header::CACHE_CONTROL, "max-age=86400"),
        ],
        bytes,
    )
        .into_response())
}

pub async fn thumb(
    State(st): State<AppState>,
    AxPath((vid, idx)): AxPath<(String, u32)>,
    Query(q): Query<ClientQuery>,
    headers: HeaderMap,
) -> Result<Response, ApiErr> {
    let client = client_from(&q, &headers)?;
    serve_img(&st, &client, &vid, "thumbs", idx).await
}

pub async fn frame(
    State(st): State<AppState>,
    AxPath((vid, idx)): AxPath<(String, u32)>,
    Query(q): Query<ClientQuery>,
    headers: HeaderMap,
) -> Result<Response, ApiErr> {
    let client = client_from(&q, &headers)?;
    serve_img(&st, &client, &vid, "frames", idx).await
}
