use std::path::PathBuf;
use spacesh_proto::codec::{read_frame, write_frame};
use spacesh_proto::{Cmd, Envelope, SurfaceId};
use spacesh_proto::status::SurfaceState;
use spacesh_cli::client;
use tokio::net::UnixListener;

// These tests mutate the process-global SPACESH_SOCK env var, so they must not
// run concurrently. Serialize them on a process-wide lock (poison-tolerant).
static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn serial() -> std::sync::MutexGuard<'static, ()> {
    SERIAL.lock().unwrap_or_else(|e| e.into_inner())
}

fn tmp_sock(name: &str) -> PathBuf {
    let mut p = std::env::temp_dir();
    let n = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
    p.push(format!("spacesh-cli-{name}-{n}.sock"));
    p
}

/// One-shot mock daemon: accept one connection, read one request, send `reply`.
fn mock_daemon(sock: PathBuf, reply: Envelope) {
    let listener = UnixListener::bind(&sock).unwrap();
    tokio::spawn(async move {
        if let Ok((mut stream, _)) = listener.accept().await {
            if let Ok(Some(_req)) = read_frame(&mut stream).await {
                let _ = write_frame(&mut stream, &reply).await;
            }
        }
    });
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_returns_data_from_daemon() {
    let _g = serial();
    let sock = tmp_sock("req");
    std::env::set_var("SPACESH_SOCK", &sock);
    mock_daemon(sock.clone(), Envelope::Res {
        id: 1, ok: true, data: serde_json::json!({ "workspace_id": "w_1" }), error: None,
    });
    tokio::time::sleep(std::time::Duration::from_millis(50)).await; // let the listener bind

    let data = client::request(Cmd::Open { path: "/tmp".into() }).await.unwrap();
    std::env::remove_var("SPACESH_SOCK");
    let _ = std::fs::remove_file(&sock);
    assert_eq!(data["workspace_id"], "w_1");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn request_surfaces_daemon_error() {
    let _g = serial();
    let sock = tmp_sock("err");
    std::env::set_var("SPACESH_SOCK", &sock);
    mock_daemon(sock.clone(), Envelope::Res {
        id: 1, ok: false, data: serde_json::Value::Null,
        error: Some(spacesh_proto::ErrorBody { code: "NOT_FOUND".into(), msg: "surface".into() }),
    });
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    let res = client::request(Cmd::Close { surface_id: SurfaceId("s_x".into()) }).await;
    std::env::remove_var("SPACESH_SOCK");
    let _ = std::fs::remove_file(&sock);
    assert!(res.is_err());
    assert!(res.unwrap_err().to_string().contains("NOT_FOUND"));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn notify_with_no_daemon_is_silent_success() {
    let _g = serial();
    let sock = tmp_sock("nodaemon"); // never bound
    std::env::set_var("SPACESH_SOCK", &sock);
    let r = client::notify(Cmd::SetState { surface_id: SurfaceId("s_1".into()), state: SurfaceState::Done }).await;
    std::env::remove_var("SPACESH_SOCK");
    assert!(r.is_ok(), "notify must be a silent success when no daemon is listening");
}
