use crate::acl::add_allow_ace;
use crate::acl::add_deny_write_ace;
use crate::acl::allow_null_device;
use crate::allow::AllowDenyPaths;
use crate::allow::compute_allow_paths_for_permissions;
use crate::cap::load_or_create_cap_sids;
use crate::cap::workspace_write_cap_sid_for_root;
use crate::cap::workspace_write_root_contains_path;
use crate::cap::workspace_write_root_overlaps_path;
use crate::cap::workspace_write_root_specificity;
use crate::deny_read_state::sync_persistent_deny_read_acls;
use crate::env::apply_no_network_to_env;
use crate::env::ensure_non_interactive_pager;
use crate::env::inherit_path_env;
use crate::env::normalize_null_device_env;
use crate::identity::SandboxCreds;
use crate::identity::require_logon_sandbox_creds;
use crate::logging::log_start;
use crate::path_normalization::canonicalize_path;
use crate::resolved_permissions::ResolvedWindowsSandboxPermissions;
use crate::sandbox_utils::ensure_codex_home_exists;
use crate::sandbox_utils::inject_git_safe_directory;
use crate::setup::effective_write_roots_for_permissions;
use crate::token::LocalSid;
use crate::token::create_readonly_token_with_cap;
use crate::token::create_workspace_write_token_with_caps_from;
use crate::token::get_current_token_for_restriction;
use crate::token::get_logon_sid_bytes;
use crate::workspace_acl::is_command_cwd_root;
use crate::workspace_acl::protect_workspace_agents_dir;
use crate::workspace_acl::protect_workspace_codex_dir;
use anyhow::Context;
use anyhow::Result;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::ffi::c_void;
use std::path::Path;
use std::path::PathBuf;
use windows_sys::Win32::Foundation::CloseHandle;
use windows_sys::Win32::Foundation::HANDLE;

pub(crate) struct SpawnContext {
    pub(crate) permissions: ResolvedWindowsSandboxPermissions,
    pub(crate) current_dir: PathBuf,
    pub(crate) logs_base_dir: Option<PathBuf>,
    pub(crate) uses_write_capabilities: bool,
}

pub(crate) struct ElevatedSpawnContext {
    pub(crate) sandbox_base: PathBuf,
    pub(crate) logs_base_dir: Option<PathBuf>,
    pub(crate) sandbox_creds: SandboxCreds,
    pub(crate) cap_sids: Vec<String>,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct SpawnPrepOptions {
    pub(crate) inherit_path: bool,
    pub(crate) add_git_safe_directory: bool,
}

pub(crate) struct LegacySessionSecurity {
    pub(crate) h_token: HANDLE,
    pub(crate) readonly_sid: Option<LocalSid>,
    pub(crate) readonly_sid_str: Option<String>,
    pub(crate) write_root_sids: Vec<RootCapabilitySid>,
}

pub(crate) struct RootCapabilitySid {
    pub(crate) root: PathBuf,
    pub(crate) sid: LocalSid,
    pub(crate) sid_str: String,
}

pub(crate) struct LegacyAclSids<'a> {
    pub(crate) readonly_sid: Option<&'a LocalSid>,
    pub(crate) readonly_sid_str: Option<&'a str>,
    pub(crate) write_root_sids: &'a [RootCapabilitySid],
}

fn prepare_spawn_context_common(
    permission_profile: &PermissionProfile,
    workspace_roots: &[AbsolutePathBuf],
    codex_home: &Path,
    cwd: &Path,
    env_map: &mut HashMap<String, String>,
    command: &[String],
    options: SpawnPrepOptions,
) -> Result<SpawnContext> {
    let permissions =
        ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
            permission_profile,
            workspace_roots,
        )?;

    normalize_null_device_env(env_map);
    ensure_non_interactive_pager(env_map);
    if options.inherit_path {
        inherit_path_env(env_map);
    }
    if options.add_git_safe_directory {
        inject_git_safe_directory(env_map, cwd);
    }

    ensure_codex_home_exists(codex_home)?;
    let sandbox_base = codex_home.join(".sandbox");
    std::fs::create_dir_all(&sandbox_base)?;
    let logs_base_dir = Some(sandbox_base);
    log_start(command, logs_base_dir.as_deref());

    let uses_write_capabilities = permissions.uses_write_capabilities_for_cwd(cwd, env_map);

    Ok(SpawnContext {
        permissions,
        current_dir: cwd.to_path_buf(),
        logs_base_dir,
        uses_write_capabilities,
    })
}

pub(crate) fn prepare_legacy_spawn_context(
    permission_profile: &PermissionProfile,
    workspace_roots: &[AbsolutePathBuf],
    codex_home: &Path,
    cwd: &Path,
    env_map: &mut HashMap<String, String>,
    command: &[String],
    options: SpawnPrepOptions,
) -> Result<SpawnContext> {
    let common = prepare_spawn_context_common(
        permission_profile,
        workspace_roots,
        codex_home,
        cwd,
        env_map,
        command,
        options,
    )?;
    if common.permissions.should_apply_network_block() {
        apply_no_network_to_env(env_map)?;
    }
    Ok(common)
}

pub(crate) fn prepare_legacy_session_security(
    uses_write_capabilities: bool,
    codex_home: &Path,
    cwd: &Path,
    capability_roots: impl IntoIterator<Item = PathBuf>,
) -> Result<LegacySessionSecurity> {
    let caps = load_or_create_cap_sids(codex_home)?;
    let (h_token, readonly_sid, readonly_sid_str, write_root_sids) = unsafe {
        if uses_write_capabilities {
            let write_root_sids = root_capability_sids(codex_home, cwd, capability_roots)?;
            if write_root_sids.is_empty() {
                anyhow::bail!("workspace-write sandbox has no writable root capability SIDs");
            }
            let base = get_current_token_for_restriction()?;
            let cap_ptrs: Vec<*mut c_void> = write_root_sids
                .iter()
                .map(|root| root.sid.as_ptr())
                .collect();
            let h_token = create_workspace_write_token_with_caps_from(base, cap_ptrs.as_slice());
            CloseHandle(base);
            let h_token = h_token?;
            (h_token, None, None, write_root_sids)
        } else {
            let psid = LocalSid::from_string(&caps.readonly)?;
            let (h_token, _psid) = create_readonly_token_with_cap(psid.as_ptr())?;
            (h_token, Some(psid), Some(caps.readonly), Vec::new())
        }
    };

    Ok(LegacySessionSecurity {
        h_token,
        readonly_sid,
        readonly_sid_str,
        write_root_sids,
    })
}

pub(crate) fn legacy_session_capability_roots(
    permissions: &ResolvedWindowsSandboxPermissions,
    current_dir: &Path,
    env_map: &HashMap<String, String>,
    codex_home: &Path,
) -> Vec<PathBuf> {
    let allow_paths = compute_allow_paths_for_permissions(permissions, current_dir, env_map)
        .allow
        .into_iter()
        .collect::<Vec<_>>();
    if permissions.uses_write_capabilities_for_cwd(current_dir, env_map) {
        effective_write_roots_for_permissions(
            permissions,
            current_dir,
            env_map,
            codex_home,
            Some(allow_paths.as_slice()),
        )
    } else {
        allow_paths
    }
}

pub(crate) fn root_capability_sids(
    codex_home: &Path,
    cwd: &Path,
    allow_paths: impl IntoIterator<Item = PathBuf>,
) -> Result<Vec<RootCapabilitySid>> {
    let mut roots: Vec<PathBuf> = allow_paths.into_iter().collect();
    roots.sort_by_key(|root| canonicalize_path(root.as_path()));
    roots.dedup_by(|a, b| canonicalize_path(a.as_path()) == canonicalize_path(b.as_path()));

    let mut out = Vec::with_capacity(roots.len());
    for root in roots {
        let sid_str = workspace_write_cap_sid_for_root(codex_home, cwd, &root)?;
        let sid = LocalSid::from_string(&sid_str)?;
        out.push(RootCapabilitySid { root, sid, sid_str });
    }
    Ok(out)
}

fn matching_root_capability<'a>(
    path: &Path,
    root_sids: &'a [RootCapabilitySid],
) -> Option<&'a RootCapabilitySid> {
    root_sids
        .iter()
        .filter(|root_sid| workspace_write_root_contains_path(&root_sid.root, path))
        .max_by_key(|root_sid| workspace_write_root_specificity(&root_sid.root))
}

fn deny_root_capabilities_for_path<'a>(
    path: &Path,
    root_sids: &'a [RootCapabilitySid],
) -> Vec<&'a RootCapabilitySid> {
    let matching_root_sids = root_sids
        .iter()
        .filter(|root_sid| workspace_write_root_overlaps_path(&root_sid.root, path))
        .collect::<Vec<_>>();
    if matching_root_sids.is_empty() {
        root_sids.iter().collect()
    } else {
        matching_root_sids
    }
}

pub(crate) fn allow_null_device_for_workspace_write(is_workspace_write: bool) {
    if !is_workspace_write {
        return;
    }

    unsafe {
        if let Ok(base) = get_current_token_for_restriction() {
            if let Ok(bytes) = get_logon_sid_bytes(base) {
                let mut tmp = bytes;
                let psid = tmp.as_mut_ptr() as *mut c_void;
                allow_null_device(psid);
            }
            CloseHandle(base);
        }
    }
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_legacy_session_acl_rules(
    permissions: &ResolvedWindowsSandboxPermissions,
    codex_home: &Path,
    current_dir: &Path,
    env_map: &HashMap<String, String>,
    additional_deny_read_paths: &[PathBuf],
    additional_deny_write_paths: &[PathBuf],
    acl_sids: LegacyAclSids<'_>,
) -> Result<()> {
    let AllowDenyPaths { allow, mut deny } =
        compute_allow_paths_for_permissions(permissions, current_dir, env_map);
    unsafe {
        for path in additional_deny_write_paths {
            // Explicit carveouts must exist before the command starts so the
            // sandbox cannot create them under a writable parent first.
            if !path.exists() {
                std::fs::create_dir_all(path)
                    .with_context(|| format!("create deny-write path {}", path.display()))?;
            }
            deny.insert(path.clone());
        }
        if let Some(readonly_sid) = acl_sids.readonly_sid {
            for p in &allow {
                let _ = add_allow_ace(p, readonly_sid.as_ptr());
            }
        } else {
            for p in &allow {
                let Some(root_sid) = matching_root_capability(p, acl_sids.write_root_sids) else {
                    continue;
                };
                let _ = add_allow_ace(p, root_sid.sid.as_ptr());
            }
        }
        for p in &deny {
            for root_sid in deny_root_capabilities_for_path(p, acl_sids.write_root_sids) {
                let _ = add_deny_write_ace(p, root_sid.sid.as_ptr());
            }
        }
        if !additional_deny_read_paths.is_empty() {
            if let Some(readonly_sid) = acl_sids.readonly_sid {
                let Some(readonly_sid_str) = acl_sids.readonly_sid_str else {
                    anyhow::bail!("readonly capability SID string missing");
                };
                sync_persistent_deny_read_acls(
                    codex_home,
                    readonly_sid_str,
                    additional_deny_read_paths,
                    readonly_sid.as_ptr(),
                )?;
            } else {
                for root_sid in acl_sids.write_root_sids {
                    sync_persistent_deny_read_acls(
                        codex_home,
                        &root_sid.sid_str,
                        additional_deny_read_paths,
                        root_sid.sid.as_ptr(),
                    )?;
                }
            }
        }
        for root_sid in acl_sids.write_root_sids {
            allow_null_device(root_sid.sid.as_ptr());
        }
        if let Some(readonly_sid) = acl_sids.readonly_sid {
            allow_null_device(readonly_sid.as_ptr());
        }
        if !acl_sids.write_root_sids.is_empty()
            && let Some(workspace_sid) =
                matching_root_capability(current_dir, acl_sids.write_root_sids)
        {
            let canonical_cwd = canonicalize_path(current_dir);
            if is_command_cwd_root(&workspace_sid.root, &canonical_cwd) {
                let _ = protect_workspace_codex_dir(current_dir, workspace_sid.sid.as_ptr());
                let _ = protect_workspace_agents_dir(current_dir, workspace_sid.sid.as_ptr());
            }
        }
    }
    Ok(())
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn prepare_elevated_spawn_context_for_permissions(
    permissions: ResolvedWindowsSandboxPermissions,
    codex_home: &Path,
    cwd: &Path,
    env_map: &mut HashMap<String, String>,
    command: &[String],
    read_roots_override: Option<&[PathBuf]>,
    read_roots_include_platform_defaults: bool,
    write_roots_override: Option<&[PathBuf]>,
    deny_read_paths_override: &[PathBuf],
    deny_write_paths_override: &[PathBuf],
) -> Result<ElevatedSpawnContext> {
    normalize_null_device_env(env_map);
    ensure_non_interactive_pager(env_map);
    inherit_path_env(env_map);
    inject_git_safe_directory(env_map, cwd);

    // Use a temp-based log dir that the sandbox user can write.
    let sandbox_base = codex_home.join(".sandbox");
    ensure_codex_home_exists(&sandbox_base)?;
    let logs_base_dir = Some(sandbox_base.clone());
    log_start(command, logs_base_dir.as_deref());

    let uses_write_capabilities = permissions.uses_write_capabilities_for_cwd(cwd, env_map);

    let AllowDenyPaths { allow, deny } =
        compute_allow_paths_for_permissions(&permissions, cwd, env_map);
    let write_roots: Vec<PathBuf> = allow.into_iter().collect();
    let deny_write_paths: Vec<PathBuf> = deny.into_iter().collect();
    let computed_write_roots_override = if uses_write_capabilities {
        Some(write_roots.as_slice())
    } else {
        None
    };
    let write_roots_for_setup = write_roots_override.or(computed_write_roots_override);
    let effective_write_roots = if uses_write_capabilities {
        effective_write_roots_for_permissions(
            &permissions,
            cwd,
            env_map,
            codex_home,
            write_roots_for_setup,
        )
    } else {
        Vec::new()
    };
    let setup_write_roots_override = if uses_write_capabilities {
        Some(effective_write_roots.as_slice())
    } else {
        write_roots_override
    };
    let sandbox_creds = require_logon_sandbox_creds(
        &permissions,
        cwd,
        env_map,
        codex_home,
        read_roots_override,
        read_roots_include_platform_defaults,
        setup_write_roots_override,
        deny_read_paths_override,
        if deny_write_paths_override.is_empty() {
            &deny_write_paths
        } else {
            deny_write_paths_override
        },
        /*proxy_enforced*/ false,
    )?;
    let caps = load_or_create_cap_sids(codex_home)?;
    let (psid_to_use, cap_sids) = if uses_write_capabilities {
        let cap_sids = root_capability_sids(codex_home, cwd, effective_write_roots)?
            .into_iter()
            .map(|root_sid| root_sid.sid_str)
            .collect::<Vec<_>>();
        if cap_sids.is_empty() {
            anyhow::bail!("workspace-write sandbox has no writable root capability SIDs");
        }
        (LocalSid::from_string(&cap_sids[0])?, cap_sids)
    } else {
        (
            LocalSid::from_string(&caps.readonly)?,
            vec![caps.readonly.clone()],
        )
    };

    unsafe {
        allow_null_device(psid_to_use.as_ptr());
    }

    Ok(ElevatedSpawnContext {
        sandbox_base,
        logs_base_dir,
        sandbox_creds,
        cap_sids,
    })
}

#[cfg(test)]
mod tests {
    use super::SpawnPrepOptions;
    use super::deny_root_capabilities_for_path;
    use super::legacy_session_capability_roots;
    use super::prepare_legacy_spawn_context;
    use super::prepare_spawn_context_common;
    use super::root_capability_sids;
    use crate::cap::load_or_create_cap_sids;
    use crate::cap::workspace_write_cap_sid_for_root;
    use crate::resolved_permissions::ResolvedWindowsSandboxPermissions;
    use codex_protocol::models::PermissionProfile;
    use codex_protocol::permissions::NetworkSandboxPolicy;
    use codex_utils_absolute_path::AbsolutePathBuf;
    use pretty_assertions::assert_eq;
    use std::collections::HashMap;
    use std::path::Path;
    use tempfile::TempDir;

    fn workspace_profile(
        network_policy: NetworkSandboxPolicy,
        writable_roots: &[AbsolutePathBuf],
        exclude_tmpdir_env_var: bool,
        exclude_slash_tmp: bool,
    ) -> PermissionProfile {
        PermissionProfile::workspace_write_with(
            writable_roots,
            network_policy,
            exclude_tmpdir_env_var,
            exclude_slash_tmp,
        )
    }

    fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf> {
        vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")]
    }

    fn should_apply_network_block(permission_profile: &PermissionProfile) -> bool {
        ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
            permission_profile,
            &[],
        )
        .expect("managed permission profile")
        .should_apply_network_block()
    }

    #[test]
    fn no_network_env_rewrite_applies_for_workspace_write() {
        assert!(should_apply_network_block(
            &PermissionProfile::workspace_write()
        ));
    }

    #[test]
    fn no_network_env_rewrite_skips_when_network_access_is_allowed() {
        assert!(!should_apply_network_block(&workspace_profile(
            NetworkSandboxPolicy::Enabled,
            &[],
            /*exclude_tmpdir_env_var*/ false,
            /*exclude_slash_tmp*/ false,
        )));
    }

    #[test]
    fn legacy_spawn_env_applies_offline_network_rewrite() {
        let codex_home = TempDir::new().expect("tempdir");
        let cwd = TempDir::new().expect("tempdir");
        let mut env_map = HashMap::new();
        let workspace_roots = workspace_roots_for(cwd.path());

        let _context = prepare_legacy_spawn_context(
            &PermissionProfile::workspace_write(),
            workspace_roots.as_slice(),
            codex_home.path(),
            cwd.path(),
            &mut env_map,
            &["cmd.exe".to_string()],
            SpawnPrepOptions {
                inherit_path: true,
                add_git_safe_directory: false,
            },
        )
        .expect("legacy env prep");

        assert_eq!(env_map.get("SBX_NONET_ACTIVE"), Some(&"1".to_string()));
        assert_eq!(
            env_map.get("HTTP_PROXY"),
            Some(&"http://127.0.0.1:9".to_string())
        );
    }

    #[test]
    fn common_spawn_env_keeps_network_env_unchanged() {
        let codex_home = TempDir::new().expect("tempdir");
        let cwd = TempDir::new().expect("tempdir");
        let mut env_map = HashMap::from([(
            "HTTP_PROXY".to_string(),
            "http://user.proxy:8080".to_string(),
        )]);
        let workspace_roots = workspace_roots_for(cwd.path());

        let context = prepare_spawn_context_common(
            &PermissionProfile::workspace_write(),
            workspace_roots.as_slice(),
            codex_home.path(),
            cwd.path(),
            &mut env_map,
            &["cmd.exe".to_string()],
            SpawnPrepOptions {
                inherit_path: true,
                add_git_safe_directory: true,
            },
        )
        .expect("preserve existing env prep");
        assert!(context.uses_write_capabilities);

        assert_eq!(env_map.get("SBX_NONET_ACTIVE"), None);
        assert_eq!(
            env_map.get("HTTP_PROXY"),
            Some(&"http://user.proxy:8080".to_string())
        );
    }

    #[test]
    fn legacy_session_capability_roots_use_runtime_workspace_roots_for_workspace_root() {
        let tmp = TempDir::new().expect("tempdir");
        let codex_home = tmp.path().join("codex-home");
        let workspace_root = tmp.path().join("workspace");
        let command_cwd = workspace_root.join("subdir");
        std::fs::create_dir_all(&codex_home).expect("create codex home");
        std::fs::create_dir_all(&command_cwd).expect("create command cwd");

        let permission_profile = workspace_profile(
            NetworkSandboxPolicy::Restricted,
            &[],
            /*exclude_tmpdir_env_var*/ true,
            /*exclude_slash_tmp*/ true,
        );
        let workspace_roots = workspace_roots_for(workspace_root.as_path());
        let permissions =
            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
                &permission_profile,
                workspace_roots.as_slice(),
            )
            .expect("managed permission profile");

        let roots = legacy_session_capability_roots(
            &permissions,
            &command_cwd,
            &HashMap::new(),
            &codex_home,
        );

        assert_eq!(
            roots,
            vec![dunce::canonicalize(&workspace_root).expect("canonical workspace root")]
        );
    }

    #[test]
    fn root_capability_sids_only_include_active_roots() {
        let temp = TempDir::new().expect("tempdir");
        let codex_home = temp.path().join("codex-home");
        let workspace = temp.path().join("workspace");
        let active_root = temp.path().join("active-root");
        let stale_root = temp.path().join("stale-root");
        std::fs::create_dir_all(&codex_home).expect("create codex home");
        std::fs::create_dir_all(&workspace).expect("create workspace");
        std::fs::create_dir_all(&active_root).expect("create active root");
        std::fs::create_dir_all(&stale_root).expect("create stale root");

        let stale_sid = workspace_write_cap_sid_for_root(&codex_home, &workspace, &stale_root)
            .expect("stale sid");
        let active_sid = workspace_write_cap_sid_for_root(&codex_home, &workspace, &active_root)
            .expect("active sid");
        let workspace_sid = workspace_write_cap_sid_for_root(&codex_home, &workspace, &workspace)
            .expect("workspace sid");
        let caps = load_or_create_cap_sids(&codex_home).expect("load caps");

        let sid_strs = root_capability_sids(
            &codex_home,
            &workspace,
            vec![workspace.clone(), active_root],
        )
        .expect("root capabilities")
        .into_iter()
        .map(|root_sid| root_sid.sid_str)
        .collect::<Vec<_>>();

        assert_eq!(sid_strs.len(), 2);
        assert!(sid_strs.contains(&workspace_sid));
        assert!(sid_strs.contains(&active_sid));
        assert!(!sid_strs.contains(&stale_sid));
        assert!(!sid_strs.contains(&caps.workspace));
    }

    #[test]
    fn legacy_deny_path_includes_nested_active_root_sid() {
        let temp = TempDir::new().expect("tempdir");
        let codex_home = temp.path().join("codex-home");
        let workspace = temp.path().join("workspace");
        let protected_dir = workspace.join(".codex");
        let nested_root = protected_dir.join("nested-root");
        let unrelated_root = temp.path().join("unrelated-root");
        std::fs::create_dir_all(&codex_home).expect("create codex home");
        std::fs::create_dir_all(&workspace).expect("create workspace");
        std::fs::create_dir_all(&nested_root).expect("create nested root");
        std::fs::create_dir_all(&unrelated_root).expect("create unrelated root");

        let workspace_sid = workspace_write_cap_sid_for_root(&codex_home, &workspace, &workspace)
            .expect("workspace sid");
        let nested_sid = workspace_write_cap_sid_for_root(&codex_home, &workspace, &nested_root)
            .expect("nested sid");
        let unrelated_sid =
            workspace_write_cap_sid_for_root(&codex_home, &workspace, &unrelated_root)
                .expect("unrelated sid");
        let root_sids = root_capability_sids(
            &codex_home,
            &workspace,
            vec![workspace.clone(), nested_root, unrelated_root],
        )
        .expect("root capabilities");

        let deny_sid_strs = deny_root_capabilities_for_path(&protected_dir, &root_sids)
            .into_iter()
            .map(|root_sid| root_sid.sid_str.clone())
            .collect::<Vec<_>>();

        assert_eq!(deny_sid_strs, vec![workspace_sid, nested_sid]);
        assert!(!deny_sid_strs.contains(&unrelated_sid));
    }

    #[test]
    fn legacy_capability_roots_use_effective_write_roots() {
        let temp = TempDir::new().expect("tempdir");
        let codex_home = temp.path().join("codex-home");
        let workspace = temp.path().join("workspace");
        let active_root = temp.path().join("active-root");
        let sandbox_root = codex_home.join(".sandbox");
        std::fs::create_dir_all(&codex_home).expect("create codex home");
        std::fs::create_dir_all(&workspace).expect("create workspace");
        std::fs::create_dir_all(&active_root).expect("create active root");
        std::fs::create_dir_all(&sandbox_root).expect("create sandbox root");

        let writable_roots = vec![
            AbsolutePathBuf::try_from(active_root.as_path()).expect("active root"),
            AbsolutePathBuf::try_from(codex_home.as_path()).expect("codex home"),
            AbsolutePathBuf::try_from(sandbox_root.as_path()).expect("sandbox root"),
        ];
        let permission_profile = workspace_profile(
            NetworkSandboxPolicy::Restricted,
            &writable_roots,
            /*exclude_tmpdir_env_var*/ true,
            /*exclude_slash_tmp*/ true,
        );
        let workspace_roots = workspace_roots_for(workspace.as_path());
        let permissions =
            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
                &permission_profile,
                workspace_roots.as_slice(),
            )
            .expect("managed permission profile");

        let roots =
            legacy_session_capability_roots(&permissions, &workspace, &HashMap::new(), &codex_home);

        assert!(roots.contains(&dunce::canonicalize(&workspace).expect("workspace")));
        assert!(roots.contains(&dunce::canonicalize(&active_root).expect("active root")));
        assert!(!roots.contains(&dunce::canonicalize(&codex_home).expect("codex home")));
        assert!(!roots.contains(&dunce::canonicalize(&sandbox_root).expect("sandbox root")));
    }
}
