//! Write files with 0o600 (owner-only) permissions on unix. Used by per-site
//! credential bundles since they hold session tokens that must not be
//! world-readable on multi-user systems.

use std::path::Path;

use tail_fin_common::TailFinError;

/// Write `body` to `path` with 0o600 permissions on unix. On non-unix
/// platforms, falls back to `std::fs::write` (OS default permissions). The
/// post-create `set_permissions(0o600)` call is load-bearing — POSIX
/// `O_CREAT | O_TRUNC` only honors the mode arg when the file is *newly*
/// created; a pre-existing 0o644 file would otherwise retain 0o644 after
/// the truncating write.
pub fn write_owner_only(path: &Path, body: &[u8]) -> Result<(), TailFinError> {
    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
        let mut file = std::fs::OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .mode(0o600)
            .open(path)
            .map_err(|e| TailFinError::Api(format!("Failed to write {}: {e}", path.display())))?;
        file.set_permissions(std::fs::Permissions::from_mode(0o600))
            .map_err(|e| {
                TailFinError::Api(format!("Failed to set 0600 on {}: {e}", path.display()))
            })?;
        file.write_all(body)
            .map_err(|e| TailFinError::Api(format!("Failed to write {}: {e}", path.display())))?;
    }
    #[cfg(not(unix))]
    {
        std::fs::write(path, body)
            .map_err(|e| TailFinError::Api(format!("Failed to write {}: {e}", path.display())))?;
    }
    Ok(())
}

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

    #[cfg(unix)]
    #[test]
    fn writes_new_file_with_0600() {
        use std::os::unix::fs::PermissionsExt;
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("secret.txt");
        write_owner_only(&path, b"hello").expect("write");
        let mode = std::fs::metadata(&path).unwrap().permissions().mode();
        assert_eq!(mode & 0o777, 0o600, "got {:o}", mode & 0o777);
    }

    #[cfg(unix)]
    #[test]
    fn overwrites_existing_0644_file_to_0600() {
        use std::os::unix::fs::PermissionsExt;
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("stale.txt");
        std::fs::write(&path, b"stale").unwrap();
        std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
        write_owner_only(&path, b"fresh").expect("write");
        let mode = std::fs::metadata(&path).unwrap().permissions().mode();
        assert_eq!(mode & 0o777, 0o600, "got {:o}", mode & 0o777);
        assert_eq!(std::fs::read(&path).unwrap(), b"fresh");
    }
}
