use std::path::PathBuf;
use anyhow::{Context, Result};

/// `~/.spacesh` directory, created if missing.
pub fn spacesh_dir() -> Result<PathBuf> {
    let home = dirs::home_dir().context("no home dir")?;
    let dir = home.join(".spacesh");
    std::fs::create_dir_all(&dir)?;
    Ok(dir)
}

pub fn socket_path() -> Result<PathBuf> {
    if let Ok(p) = std::env::var("SPACESH_SOCK") {
        if !p.is_empty() {
            return Ok(PathBuf::from(p));
        }
    }
    Ok(spacesh_dir()?.join("sock"))
}

pub fn lock_path() -> Result<PathBuf> {
    if let Ok(p) = std::env::var("SPACESH_LOCK") {
        if !p.is_empty() {
            return Ok(PathBuf::from(p));
        }
    }
    Ok(spacesh_dir()?.join("daemon.lock"))
}

/// Hold the single-instance lock for the lifetime of the daemon.
pub struct InstanceLock {
    _file: std::fs::File,
}

/// Acquire the exclusive daemon lock. Returns `Ok(None)` if another live daemon holds it.
pub fn acquire_instance_lock() -> Result<Option<InstanceLock>> {
    use fs2::FileExt;
    let file = std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .open(lock_path()?)?;
    match file.try_lock_exclusive() {
        Ok(()) => Ok(Some(InstanceLock { _file: file })),
        Err(_) => Ok(None),
    }
}

/// If a stale socket file exists but no daemon answers, remove it so we can bind.
pub fn clear_stale_socket() -> Result<()> {
    let path = socket_path()?;
    if path.exists() {
        // We hold the instance lock, so any existing socket is stale.
        std::fs::remove_file(&path)?;
    }
    Ok(())
}

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

    #[test]
    fn paths_live_under_spacesh_dir() {
        let _serial = crate::test_support::serial();
        let dir = spacesh_dir().unwrap();
        assert!(socket_path().unwrap().starts_with(&dir));
        assert!(lock_path().unwrap().starts_with(&dir));
    }

    #[test]
    fn lock_is_exclusive_within_process() {
        let _serial = crate::test_support::serial();
        // Use a private lock path so a real running daemon (which holds the
        // global ~/.spacesh/daemon.lock) can't make this test flake.
        let tmp = std::env::temp_dir().join("spacesh-lock-exclusive-test.lock");
        std::env::set_var("SPACESH_LOCK", &tmp);
        let first = acquire_instance_lock().unwrap();
        assert!(first.is_some(), "first acquire should succeed");
        // A second attempt from the same process on the same fd path:
        // fs2 advisory locks are per-handle; opening a new handle and locking
        // should fail while `first` is held.
        let second = acquire_instance_lock().unwrap();
        assert!(second.is_none(), "second acquire should be blocked");
        drop(first);
        std::env::remove_var("SPACESH_LOCK");
        let _ = std::fs::remove_file(&tmp);
    }

    #[test]
    fn socket_path_honors_env_override() {
        let _serial = crate::test_support::serial();
        // Note: set/remove around the assertion; serialized against the other
        // env-sensitive lifecycle test via the crate's serial() lock.
        std::env::set_var("SPACESH_SOCK", "/tmp/spacesh-test-override.sock");
        let p = socket_path().unwrap();
        std::env::remove_var("SPACESH_SOCK");
        assert_eq!(p, std::path::PathBuf::from("/tmp/spacesh-test-override.sock"));
    }
}
