use std::sync::Arc;

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

use crate::app::AppState;

#[derive(Serialize)]
pub struct Diagnostics {
    application: &'static str,
    version: &'static str,
    build: &'static str,
    platform: String,
    ffmpeg: ToolInfo,
    ffprobe: ToolInfo,
    media_root: String,
    bind: String,
}

#[derive(Serialize)]
struct ToolInfo {
    path: String,
    version: String,
}

pub async fn info(State(state): State<Arc<AppState>>) -> Json<Diagnostics> {
    let (ffmpeg, ffprobe) = tokio::join!(
        tool_info(&state.config.ffmpeg),
        tool_info(&state.config.ffprobe)
    );
    Json(Diagnostics {
        application: env!("CARGO_PKG_NAME"),
        version: env!("CARGO_PKG_VERSION"),
        build: if cfg!(debug_assertions) {
            "debug"
        } else {
            "release"
        },
        platform: format!("{} / {}", std::env::consts::OS, std::env::consts::ARCH),
        ffmpeg,
        ffprobe,
        media_root: state.config.media_root.display().to_string(),
        bind: state.config.bind.to_string(),
    })
}

async fn tool_info(path: &std::path::Path) -> ToolInfo {
    let version = match Command::new(path).arg("-version").output().await {
        Ok(output) if output.status.success() => first_line(&output.stdout),
        Ok(output) => format!("Unavailable ({})", output.status),
        Err(error) => format!("Unavailable ({error})"),
    };
    ToolInfo {
        path: path.display().to_string(),
        version,
    }
}

fn first_line(output: &[u8]) -> String {
    String::from_utf8_lossy(output)
        .lines()
        .next()
        .unwrap_or("Unknown")
        .trim()
        .to_owned()
}

#[cfg(test)]
mod tests {
    use super::first_line;

    #[test]
    fn keeps_only_the_tool_version_line() {
        assert_eq!(
            first_line(b"ffmpeg version 7.1\nconfiguration: test\n"),
            "ffmpeg version 7.1"
        );
    }
}
