use serde_json::Value;
use tokio::io::{
    AsyncBufReadExt,
    AsyncReadExt,
    AsyncWriteExt,
    BufReader,
};
use tokio::net::UnixStream;

pub fn socket_path() -> String {
    use std::sync::atomic::{AtomicU32, Ordering};
    static N: AtomicU32 = AtomicU32::new(0);
    let n = N.fetch_add(1, Ordering::Relaxed);
    format!(
        "/tmp/t4a-test-{}-{n}.sock",
        std::process::id()
    )
}

pub async fn request(
    socket: &str,
    req: &Value,
) -> Value {
    let stream =
        UnixStream::connect(socket).await.unwrap();
    let (reader, mut writer) = stream.into_split();
    let mut buf = serde_json::to_vec(req).unwrap();
    buf.push(b'\n');
    writer.write_all(&buf).await.unwrap();

    let mut reader = BufReader::new(reader);
    let mut line = String::new();
    reader.read_line(&mut line).await.unwrap();
    serde_json::from_str(&line).unwrap()
}

pub async fn request_binary(
    socket: &str,
    req: &Value,
) -> (Value, Vec<u8>) {
    let stream =
        UnixStream::connect(socket).await.unwrap();
    let (reader, mut writer) = stream.into_split();
    let mut buf = serde_json::to_vec(req).unwrap();
    buf.push(b'\n');
    writer.write_all(&buf).await.unwrap();

    let mut reader = BufReader::new(reader);
    let mut line = String::new();
    reader.read_line(&mut line).await.unwrap();
    let header: Value =
        serde_json::from_str(&line).unwrap();
    let len =
        header["len"].as_u64().unwrap() as usize;
    let mut data = vec![0u8; len];
    reader.read_exact(&mut data).await.unwrap();
    (header, data)
}

pub fn start_daemon(sock: &str) -> std::process::Child {
    let exe = env!("CARGO_BIN_EXE_t4a");
    std::process::Command::new(exe)
        .args(["daemon"])
        .env("T4A_SOCKET", sock)
        .stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::null())
        .stderr(std::process::Stdio::null())
        .spawn()
        .expect("failed to start daemon")
}

pub async fn wait_for_socket(sock: &str) {
    for _ in 0..100 {
        if UnixStream::connect(sock).await.is_ok() {
            return;
        }
        tokio::time::sleep(
            std::time::Duration::from_millis(50),
        )
        .await;
    }
    panic!("daemon did not start");
}
