//! Auto UID/GID mapping policy for VM mode.
//!
//! When a normal (non-root) host user runs a VM, files created inside the guest
//! appear with mismatched UIDs/GIDs on the host. This module provides automatic
//! generation of sensible UID/GID mappings for virtiofs/libkrun.
//!
//! # Policy
//!
//! - All guest UIDs are squashed to the current host UID
//! - All guest GIDs are squashed to the current host GID
//! - Host UID is mapped back to the requested guest UID (from --user option)
//! - Host GID is mapped back to the requested guest GID
//!
//! # Result
//!
//! - Files created by any user in guest → owned by host user on host filesystem
//! - Files owned by host user on host → appear as owned by --user in guest

/// Maximum UID/GID value for squash mapping
#[cfg(any(feature = "libkrun", target_os = "linux"))]
const MAX_ID: u32 = 4294967295;

/// Auto-generate UID/GID mapping specs for VM mode.
///
/// # Arguments
///
/// * `host_uid` - Current host user's UID
/// * `host_gid` - Current host user's GID
/// * `guest_user` - Requested guest user from --user option (None means root)
///
/// # Returns
///
/// (uid_specs, gid_specs) - Vectors of mapping spec strings for --translate-uid/gid
///
/// # Example
///
/// For host UID=501, GID=20, --user=root:
/// ```text
/// uid_specs: ["squash-guest:0:501:4294967295", "host:501:0:1"]
/// gid_specs: ["squash-guest:0:20:4294967295", "host:20:0:1"]
/// ```
#[cfg(any(feature = "libkrun", target_os = "linux"))]
pub fn auto_idmap_specs(
    host_uid: u32,
    host_gid: u32,
    guest_user: Option<&str>,
) -> (Vec<String>, Vec<String>) {
    let (guest_uid, guest_gid) = resolve_guest_user(guest_user);

    // Generate mappings:
    // 1. squash all guest UIDs/GIDs to host UID/GID
    // 2. reverse map host UID/GID to guest UID/GID
    let uid_specs = vec![
        format!("squash-guest:0:{}:{}", host_uid, MAX_ID),
        format!("host:{}:{}:1", host_uid, guest_uid),
    ];
    let gid_specs = vec![
        format!("squash-guest:0:{}:{}", host_gid, MAX_ID),
        format!("host:{}:{}:1", host_gid, guest_gid),
    ];

    (uid_specs, gid_specs)
}

/// Check if auto-mapping should be applied.
///
/// Returns true if:
/// - Host user is non-root (root doesn't need mapping)
/// - Running in VM mode (caller's responsibility to check)
#[cfg(any(feature = "libkrun", target_os = "linux"))]
pub fn should_auto_map(host_uid: u32) -> bool {
    host_uid != 0
}

/// Merge auto-generated specs with user-specified specs.
///
/// User-specified specs are appended after auto specs.
/// In virtiofsd, later rules have higher priority.
#[cfg(any(feature = "libkrun", target_os = "linux"))]
pub fn merge_idmap_specs(
    auto_specs: Vec<String>,
    user_specs: Vec<String>,
) -> Vec<String> {
    [auto_specs, user_specs].concat()
}

/// Resolve guest user string to (UID, GID).
///
/// - None or "root" → (0, 0)
/// - Numeric string like "1000" → (1000, 1000)  (GID defaults to UID)
/// - Username → TODO: lookup from environment's /etc/passwd
#[cfg(any(feature = "libkrun", target_os = "linux"))]
fn resolve_guest_user(user: Option<&str>) -> (u32, u32) {
    match user {
        None | Some("root") => (0, 0),
        Some(uid_str) if uid_str.parse::<u32>().is_ok() => {
            let uid = uid_str.parse().unwrap();
            (uid, uid) // Default GID = UID
        }
        Some(_username) => {
            // TODO: lookup from environment's /etc/passwd
            // For now, default to root
            (0, 0)
        }
    }
}

/// Convert ID map specs to a single newline-separated string.
///
/// Used for passing to libkrun FFI.
#[cfg(feature = "libkrun")]
pub fn specs_to_string(specs: &[String]) -> String {
    specs.join("\n")
}

#[cfg(all(test, any(feature = "libkrun", target_os = "linux")))]
mod tests {
    use super::*;

    #[test]
    fn test_auto_idmap_specs_root() {
        let (uid, gid) = auto_idmap_specs(501, 20, None);
        assert_eq!(uid, vec![
            "squash-guest:0:501:4294967295",
            "host:501:0:1",
        ]);
        assert_eq!(gid, vec![
            "squash-guest:0:20:4294967295",
            "host:20:0:1",
        ]);
    }

    #[test]
    fn test_auto_idmap_specs_user_1000() {
        let (uid, gid) = auto_idmap_specs(501, 20, Some("1000"));
        assert_eq!(uid, vec![
            "squash-guest:0:501:4294967295",
            "host:501:1000:1",
        ]);
        assert_eq!(gid, vec![
            "squash-guest:0:20:4294967295",
            "host:20:1000:1",
        ]);
    }

    #[test]
    fn test_should_auto_map() {
        assert!(!should_auto_map(0));  // root - no auto map
        assert!(should_auto_map(501)); // normal user - auto map
    }

    #[test]
    fn test_merge_idmap_specs() {
        let auto = vec!["auto1".to_string(), "auto2".to_string()];
        let user = vec!["user1".to_string()];
        let merged = merge_idmap_specs(auto, user);
        assert_eq!(merged, vec!["auto1", "auto2", "user1"]);
    }
}