use std::{path::Path, process::Stdio, sync::Arc};

use axum::{
    Json,
    extract::{Query, State},
};
use serde::{Deserialize, Serialize};
use tokio::process::Command;

use crate::{
    app::AppState,
    config::Config,
    error::AppError,
    media::{PathQuery, path},
};

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Probe {
    #[serde(default)]
    pub streams: Vec<Stream>,
    pub format: Option<Format>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Stream {
    pub index: u32,
    pub codec_name: Option<String>,
    pub codec_type: Option<String>,
    pub pix_fmt: Option<String>,
    #[serde(default)]
    pub tags: serde_json::Map<String, serde_json::Value>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Format {
    pub format_name: Option<String>,
    pub duration: Option<String>,
}

pub async fn probe(
    State(state): State<Arc<AppState>>,
    Query(query): Query<PathQuery>,
) -> Result<Json<Probe>, AppError> {
    let media = path::resolve(&state.config, &query.path)?;
    Ok(Json(run(&state.config, &media).await?))
}

pub async fn run(config: &Config, media: &Path) -> Result<Probe, AppError> {
    let output = Command::new(&config.ffprobe)
        .args([
            "-v",
            "error",
            "-show_format",
            "-show_streams",
            "-of",
            "json",
        ])
        .arg(media)
        .stdin(Stdio::null())
        .output()
        .await
        .map_err(AppError::internal)?;
    if !output.status.success() {
        return Err(AppError::internal(String::from_utf8_lossy(&output.stderr)));
    }
    serde_json::from_slice(&output.stdout).map_err(AppError::internal)
}
