use std::path::{Path, PathBuf};

use anyhow::{Context, Result};

fn data_dir() -> PathBuf {
    dirs::data_local_dir()
        .unwrap_or_else(|| dirs::home_dir().unwrap_or_default().join(".local/share"))
        .join("lean-ctx")
}

pub(super) fn default_socket_path() -> PathBuf {
    data_dir().join("daemon.sock")
}

pub(super) fn bind_listener(path: &Path) -> Result<tokio::net::UnixListener> {
    if path.exists() {
        std::fs::remove_file(path)
            .with_context(|| format!("remove stale socket {}", path.display()))?;
    }

    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| format!("create socket dir {}", parent.display()))?;
    }

    let listener = tokio::net::UnixListener::bind(path)
        .with_context(|| format!("bind UDS {}", path.display()))?;

    use std::os::unix::fs::PermissionsExt;
    let perms = std::fs::Permissions::from_mode(0o600);
    std::fs::set_permissions(path, perms)
        .with_context(|| format!("chmod 600 UDS {}", path.display()))?;

    Ok(listener)
}

pub(super) async fn connect(path: &Path) -> Result<tokio::net::UnixStream> {
    tokio::net::UnixStream::connect(path)
        .await
        .with_context(|| format!("connect to daemon at {}", path.display()))
}

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

    #[test]
    fn default_socket_path_ends_with_sock() {
        let p = default_socket_path();
        assert!(p.ends_with("daemon.sock"), "got: {}", p.display());
    }

    #[tokio::test]
    async fn bind_and_connect() {
        let dir = tempfile::tempdir().unwrap();
        let sock = dir.path().join("test.sock");

        let listener = bind_listener(&sock).unwrap();
        assert!(sock.exists());

        let perms = std::fs::metadata(&sock).unwrap().permissions();
        use std::os::unix::fs::PermissionsExt;
        assert_eq!(perms.mode() & 0o777, 0o600);

        let connect_fut = connect(&sock);
        let accept_fut = listener.accept();
        let (conn_result, accept_result) = tokio::join!(connect_fut, accept_fut);
        assert!(conn_result.is_ok());
        assert!(accept_result.is_ok());
    }

    #[tokio::test]
    async fn bind_cleans_stale_socket() {
        let dir = tempfile::tempdir().unwrap();
        let sock = dir.path().join("stale.sock");
        std::fs::write(&sock, b"stale").unwrap();

        let _listener = bind_listener(&sock).unwrap();
        assert!(sock.exists());
    }
}
