use std::{
    collections::BTreeMap,
    ffi::{OsStr, c_void},
    io,
    os::windows::{
        ffi::OsStrExt,
        io::{FromRawHandle, RawHandle},
        process::ExitStatusExt,
    },
    path::{Path, PathBuf},
    process::ExitStatus,
    ptr,
};

use uuid::Uuid;
use windows_sys::Win32::{
    Foundation::{
        CloseHandle, ERROR_ACCESS_DENIED, ERROR_SUCCESS, GENERIC_ALL, GetLastError, HANDLE,
        HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, LUID, LocalFree, SetHandleInformation,
        WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
    },
    Security::Authorization::{
        ConvertStringSidToSidW, DENY_ACCESS, EXPLICIT_ACCESS_W, GetNamedSecurityInfoW, SET_ACCESS,
        SetEntriesInAclW, SetNamedSecurityInfoW, TRUSTEE_IS_SID, TRUSTEE_IS_UNKNOWN, TRUSTEE_W,
    },
    Security::{
        ACL, ACL_SIZE_INFORMATION, AclSizeInformation, AdjustTokenPrivileges,
        CONTAINER_INHERIT_ACE, CopySid, CreateRestrictedToken, CreateWellKnownSid,
        DACL_SECURITY_INFORMATION, DISABLE_MAX_PRIVILEGE, GetAclInformation, GetLengthSid,
        GetTokenInformation, LUA_TOKEN, LookupPrivilegeValueW, OBJECT_INHERIT_ACE, PSID,
        SID_AND_ATTRIBUTES, SetTokenInformation, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_PRIVILEGES,
        TOKEN_ADJUST_SESSIONID, TOKEN_ASSIGN_PRIMARY, TOKEN_DEFAULT_DACL, TOKEN_DUPLICATE,
        TOKEN_PRIVILEGES, TOKEN_QUERY, TokenDefaultDacl, TokenGroups, WRITE_RESTRICTED,
    },
    Storage::FileSystem::{
        CreateFileW, DELETE, FILE_APPEND_DATA, FILE_ATTRIBUTE_NORMAL, FILE_DELETE_CHILD,
        FILE_FLAG_BACKUP_SEMANTICS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
        FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_ATTRIBUTES,
        FILE_WRITE_DATA, FILE_WRITE_EA, OPEN_EXISTING,
    },
    System::{
        Console::{GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE},
        JobObjects::{
            AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
            JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobObjectExtendedLimitInformation,
            SetInformationJobObject, TerminateJobObject,
        },
        Pipes::CreatePipe,
        SystemServices::SE_GROUP_LOGON_ID,
        Threading::{
            CREATE_UNICODE_ENVIRONMENT, CreateProcessAsUserW, GetCurrentProcess,
            GetExitCodeProcess, GetProcessId, INFINITE, OpenProcessToken, PROCESS_INFORMATION,
            STARTF_USESTDHANDLES, STARTUPINFOW, TerminateProcess, WaitForSingleObject,
        },
    },
};

use super::{
    RuntimeSandboxPolicy, SandboxProcessOptions, SandboxStdio, apply_std_command_options,
    policy_paths_with_resolved,
};

const WIN_WORLD_SID: i32 = 1;
const WORKER_READ_ALLOW_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
const WORKER_WRITE_ALLOW_MASK: u32 =
    FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE | FILE_DELETE_CHILD;
const WORKER_DENY_WRITE_MASK: u32 = FILE_GENERIC_WRITE
    | FILE_WRITE_DATA
    | FILE_APPEND_DATA
    | FILE_WRITE_EA
    | FILE_WRITE_ATTRIBUTES
    | DELETE
    | FILE_DELETE_CHILD;
const WORKER_DENY_READ_MASK: u32 = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;

pub enum WindowsSandboxChild {
    Plain(std::process::Child),
    Restricted(RestrictedWindowsChild),
}

unsafe impl Send for WindowsSandboxChild {}
unsafe impl Sync for WindowsSandboxChild {}

pub struct WindowsSandboxAsyncChild {
    child: RestrictedWindowsChild,
    stdin: Option<tokio::fs::File>,
    stdout: Option<tokio::fs::File>,
    stderr: Option<tokio::fs::File>,
}

unsafe impl Send for WindowsSandboxAsyncChild {}
unsafe impl Sync for WindowsSandboxAsyncChild {}

pub struct RestrictedWindowsChild {
    process: HANDLE,
    thread: HANDLE,
    job: HANDLE,
    process_id: u32,
    exit_status: Option<ExitStatus>,
    acl_guards: Vec<AclGuard>,
    acl_cleaned: bool,
}

unsafe impl Send for RestrictedWindowsChild {}
unsafe impl Sync for RestrictedWindowsChild {}

struct LocalSid {
    ptr: PSID,
}

struct LocalMem<T>(*mut T);

struct OwnedHandle(HANDLE);

struct RestrictedToken {
    handle: OwnedHandle,
    logon_sid: Vec<u8>,
    everyone_sid: Vec<u8>,
}

struct AclGuard {
    path: PathBuf,
    original_dacl: Option<Vec<u8>>,
}

struct StartupHandles {
    stdin: HANDLE,
    stdout: HANDLE,
    stderr: HANDLE,
    _owned: Vec<OwnedHandle>,
}

#[derive(Default)]
struct ParentPipeHandles {
    stdin: Option<OwnedHandle>,
    stdout: Option<OwnedHandle>,
    stderr: Option<OwnedHandle>,
}

impl WindowsSandboxChild {
    pub fn id(&self) -> u32 {
        match self {
            Self::Plain(child) => child.id(),
            Self::Restricted(child) => child.id(),
        }
    }

    pub fn kill(&mut self) -> io::Result<()> {
        match self {
            Self::Plain(child) => child.kill(),
            Self::Restricted(child) => child.kill(),
        }
    }

    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
        match self {
            Self::Plain(child) => child.try_wait(),
            Self::Restricted(child) => child.try_wait(),
        }
    }

    pub fn wait(&mut self) -> io::Result<ExitStatus> {
        match self {
            Self::Plain(child) => child.wait(),
            Self::Restricted(child) => child.wait(),
        }
    }
}

impl WindowsSandboxAsyncChild {
    pub fn id(&self) -> u32 {
        self.child.id()
    }

    pub fn take_stdin(&mut self) -> Option<tokio::fs::File> {
        self.stdin.take()
    }

    pub fn take_stdout(&mut self) -> Option<tokio::fs::File> {
        self.stdout.take()
    }

    pub fn take_stderr(&mut self) -> Option<tokio::fs::File> {
        self.stderr.take()
    }

    pub fn start_kill(&mut self) -> io::Result<()> {
        self.child.kill()
    }

    pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
        self.child.try_wait()
    }
}

pub fn spawn_plain(
    policy: &RuntimeSandboxPolicy,
    program: PathBuf,
    args: Vec<String>,
    options: SandboxProcessOptions,
) -> io::Result<WindowsSandboxChild> {
    let mut command = std::process::Command::new(program);
    command.args(args);
    apply_std_command_options(policy, &mut command, options);
    command.spawn().map(WindowsSandboxChild::Plain)
}

pub fn spawn_restricted(
    policy: &RuntimeSandboxPolicy,
    program: PathBuf,
    args: Vec<String>,
    options: SandboxProcessOptions,
) -> io::Result<WindowsSandboxChild> {
    let cap_sid = random_capability_sid();
    let cap_sid_ptr = LocalSid::from_string(&cap_sid)?;
    let token = create_restricted_token(&cap_sid_ptr)?;
    let mut acl_guards = Vec::new();
    let process = match spawn_restricted_inner(
        policy,
        &program,
        args,
        options,
        cap_sid_ptr.as_ptr(),
        token.logon_sid.as_ptr().cast::<c_void>().cast_mut(),
        token.everyone_sid.as_ptr().cast::<c_void>().cast_mut(),
        token.handle.raw(),
        &mut acl_guards,
    ) {
        Ok(process) => process,
        Err(err) => {
            restore_acl_guards(&acl_guards);
            return Err(err);
        }
    };
    drop(token);
    Ok(WindowsSandboxChild::Restricted(process))
}

pub fn spawn_restricted_async(
    policy: &RuntimeSandboxPolicy,
    program: PathBuf,
    args: Vec<String>,
    options: SandboxProcessOptions,
) -> io::Result<WindowsSandboxAsyncChild> {
    let cap_sid = random_capability_sid();
    let cap_sid_ptr = LocalSid::from_string(&cap_sid)?;
    let token = create_restricted_token(&cap_sid_ptr)?;
    let mut acl_guards = Vec::new();
    let process = match spawn_restricted_async_inner(
        policy,
        &program,
        args,
        options,
        cap_sid_ptr.as_ptr(),
        token.logon_sid.as_ptr().cast::<c_void>().cast_mut(),
        token.everyone_sid.as_ptr().cast::<c_void>().cast_mut(),
        token.handle.raw(),
        &mut acl_guards,
    ) {
        Ok(process) => process,
        Err(err) => {
            restore_acl_guards(&acl_guards);
            return Err(err);
        }
    };
    drop(token);
    Ok(process)
}

#[allow(clippy::too_many_arguments)]
fn spawn_restricted_inner(
    policy: &RuntimeSandboxPolicy,
    program: &Path,
    args: Vec<String>,
    options: SandboxProcessOptions,
    psid_capability: PSID,
    psid_logon: PSID,
    psid_everyone: PSID,
    token: HANDLE,
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<RestrictedWindowsChild> {
    apply_policy_acl_rules(
        policy,
        program,
        psid_capability,
        psid_logon,
        psid_everyone,
        acl_guards,
    )?;
    let current_dir = options
        .current_dir
        .clone()
        .unwrap_or(std::env::current_dir()?);
    let argv = command_argv(program, args);
    let mut command_line = to_wide(argv_to_command_line(&argv));
    let program_wide = to_wide(program.as_os_str());
    let current_dir_wide = to_wide(current_dir.as_os_str());
    let env_block = environment_block(policy);
    let stdio = StartupHandles::new(options)?;
    let startup_info = startup_info(&stdio);
    let mut process_info = PROCESS_INFORMATION::default();

    let created = unsafe {
        CreateProcessAsUserW(
            token,
            program_wide.as_ptr(),
            command_line.as_mut_ptr(),
            ptr::null(),
            ptr::null(),
            1,
            CREATE_UNICODE_ENVIRONMENT,
            env_block.as_ptr().cast::<c_void>(),
            current_dir_wide.as_ptr(),
            &startup_info,
            &mut process_info,
        )
    };
    if created == 0 {
        return Err(last_os_error("CreateProcessAsUserW failed"));
    }

    let job = match create_kill_on_close_job(process_info.hProcess) {
        Ok(job) => job,
        Err(err) => {
            unsafe {
                TerminateProcess(process_info.hProcess, 1);
                CloseHandle(process_info.hThread);
                CloseHandle(process_info.hProcess);
            }
            return Err(err);
        }
    };

    Ok(RestrictedWindowsChild {
        process: process_info.hProcess,
        thread: process_info.hThread,
        job,
        process_id: unsafe { GetProcessId(process_info.hProcess) },
        exit_status: None,
        acl_guards: std::mem::take(acl_guards),
        acl_cleaned: false,
    })
}

#[allow(clippy::too_many_arguments)]
fn spawn_restricted_async_inner(
    policy: &RuntimeSandboxPolicy,
    program: &Path,
    args: Vec<String>,
    options: SandboxProcessOptions,
    psid_capability: PSID,
    psid_logon: PSID,
    psid_everyone: PSID,
    token: HANDLE,
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<WindowsSandboxAsyncChild> {
    apply_policy_acl_rules(
        policy,
        program,
        psid_capability,
        psid_logon,
        psid_everyone,
        acl_guards,
    )?;
    let current_dir = options
        .current_dir
        .clone()
        .unwrap_or(std::env::current_dir()?);
    let argv = command_argv(program, args);
    let mut command_line = to_wide(argv_to_command_line(&argv));
    let program_wide = to_wide(program.as_os_str());
    let current_dir_wide = to_wide(current_dir.as_os_str());
    let env_block = environment_block(policy);
    let (stdio, parent_pipes) = StartupHandles::new_with_parent_pipes(options)?;
    let startup_info = startup_info(&stdio);
    let mut process_info = PROCESS_INFORMATION::default();

    let created = unsafe {
        CreateProcessAsUserW(
            token,
            program_wide.as_ptr(),
            command_line.as_mut_ptr(),
            ptr::null(),
            ptr::null(),
            1,
            CREATE_UNICODE_ENVIRONMENT,
            env_block.as_ptr().cast::<c_void>(),
            current_dir_wide.as_ptr(),
            &startup_info,
            &mut process_info,
        )
    };
    if created == 0 {
        return Err(last_os_error("CreateProcessAsUserW failed"));
    }

    let job = match create_kill_on_close_job(process_info.hProcess) {
        Ok(job) => job,
        Err(err) => {
            unsafe {
                TerminateProcess(process_info.hProcess, 1);
                CloseHandle(process_info.hThread);
                CloseHandle(process_info.hProcess);
            }
            return Err(err);
        }
    };

    let child = RestrictedWindowsChild {
        process: process_info.hProcess,
        thread: process_info.hThread,
        job,
        process_id: unsafe { GetProcessId(process_info.hProcess) },
        exit_status: None,
        acl_guards: std::mem::take(acl_guards),
        acl_cleaned: false,
    };
    let (stdin, stdout, stderr) = parent_pipes.into_async_files()?;

    Ok(WindowsSandboxAsyncChild {
        child,
        stdin,
        stdout,
        stderr,
    })
}

fn apply_policy_acl_rules(
    policy: &RuntimeSandboxPolicy,
    program: &Path,
    psid: PSID,
    psid_logon: PSID,
    psid_everyone: PSID,
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<()> {
    if let Some(parent) = program.parent() {
        add_program_read_ace(parent, psid, acl_guards)?;
    }
    add_program_read_ace(program, psid, acl_guards)?;

    for root in policy_paths_with_resolved(&policy.filesystem.readable_roots) {
        add_guarded_ace(&root, psid, WORKER_READ_ALLOW_MASK, SET_ACCESS, acl_guards)?;
    }
    for writable_root in &policy.filesystem.writable_roots {
        for root in policy_paths_with_resolved(std::slice::from_ref(&writable_root.root)) {
            add_guarded_ace(&root, psid, WORKER_WRITE_ALLOW_MASK, SET_ACCESS, acl_guards)?;
        }
        for subpath in policy_paths_with_resolved(&writable_root.read_only_subpaths) {
            add_guarded_ace_recursive(
                &subpath,
                psid,
                WORKER_DENY_WRITE_MASK,
                DENY_ACCESS,
                acl_guards,
            )?;
        }
    }
    for path in policy_paths_with_resolved(&policy.filesystem.deny_write_paths) {
        add_guarded_ace_recursive(&path, psid, WORKER_DENY_WRITE_MASK, DENY_ACCESS, acl_guards)?;
    }
    // File reads can be satisfied through the broader restricted SIDs, so
    // file-level deny-read guards must cover them as well as the capability SID.
    let deny_read_entries = [
        (psid, WORKER_DENY_READ_MASK, DENY_ACCESS),
        (psid_logon, WORKER_DENY_READ_MASK, DENY_ACCESS),
        (psid_everyone, WORKER_DENY_READ_MASK, DENY_ACCESS),
    ];
    let deny_read_directory_entries = [(psid, WORKER_DENY_READ_MASK, DENY_ACCESS)];
    for path in policy_paths_with_resolved(&policy.filesystem.deny_read_paths) {
        add_guarded_read_denies_recursive(
            &path,
            &deny_read_entries,
            &deny_read_directory_entries,
            acl_guards,
        )?;
    }
    Ok(())
}

fn add_program_read_ace(path: &Path, psid: PSID, acl_guards: &mut Vec<AclGuard>) -> io::Result<()> {
    match add_guarded_ace(path, psid, WORKER_READ_ALLOW_MASK, SET_ACCESS, acl_guards) {
        Ok(()) => Ok(()),
        Err(err) if err.kind() == io::ErrorKind::PermissionDenied => {
            tracing::warn!(
                "Windows sandbox could not add read ACE for executable path {}; relying on existing ACLs: {err}",
                path.display()
            );
            Ok(())
        }
        Err(err) => Err(err),
    }
}

fn add_guarded_ace(
    path: &Path,
    psid: PSID,
    mask: u32,
    mode: i32,
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<()> {
    if !path.exists() {
        return Ok(());
    }
    ensure_acl_guard(path, acl_guards)?;
    add_explicit_aces(path, &[(psid, mask, mode)])?;
    Ok(())
}

fn add_guarded_aces(
    path: &Path,
    entries: &[(PSID, u32, i32)],
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<()> {
    if !path.exists() {
        return Ok(());
    }
    ensure_acl_guard(path, acl_guards)?;
    add_explicit_aces(path, entries)?;
    Ok(())
}

fn add_guarded_ace_recursive(
    path: &Path,
    psid: PSID,
    mask: u32,
    mode: i32,
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<()> {
    if !path.exists() {
        return Ok(());
    }
    if path.is_dir() {
        for entry in std::fs::read_dir(path)? {
            let entry = entry?;
            let child = entry.path();
            if entry.file_type()?.is_dir() {
                add_guarded_ace_recursive(&child, psid, mask, mode, acl_guards)?;
            } else {
                add_guarded_ace(&child, psid, mask, mode, acl_guards)?;
            }
        }
    }
    add_guarded_ace(path, psid, mask, mode, acl_guards)?;
    Ok(())
}

fn add_guarded_read_denies_recursive(
    path: &Path,
    file_entries: &[(PSID, u32, i32)],
    directory_entries: &[(PSID, u32, i32)],
    acl_guards: &mut Vec<AclGuard>,
) -> io::Result<()> {
    if !path.exists() {
        return Ok(());
    }
    if path.is_dir() {
        for entry in std::fs::read_dir(path)? {
            let entry = entry?;
            let child = entry.path();
            if entry.file_type()?.is_dir() {
                add_guarded_read_denies_recursive(
                    &child,
                    file_entries,
                    directory_entries,
                    acl_guards,
                )?;
            } else {
                add_guarded_aces(&child, file_entries, acl_guards)?;
            }
        }
        add_guarded_aces(path, directory_entries, acl_guards)?;
    } else {
        add_guarded_aces(path, file_entries, acl_guards)?;
    }
    Ok(())
}

fn ensure_acl_guard(path: &Path, acl_guards: &mut Vec<AclGuard>) -> io::Result<()> {
    if acl_guards.iter().any(|guard| guard.path == path) {
        return Ok(());
    }
    let original_dacl = capture_dacl(path)?;
    acl_guards.push(AclGuard {
        path: path.to_path_buf(),
        original_dacl,
    });
    Ok(())
}

fn capture_dacl(path: &Path) -> io::Result<Option<Vec<u8>>> {
    let path_wide = to_wide(path.as_os_str());
    let mut security_descriptor: PSID = ptr::null_mut();
    let mut dacl = ptr::null_mut();
    let code = unsafe {
        GetNamedSecurityInfoW(
            path_wide.as_ptr(),
            1,
            DACL_SECURITY_INFORMATION,
            ptr::null_mut(),
            ptr::null_mut(),
            &mut dacl,
            ptr::null_mut(),
            &mut security_descriptor,
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            format!("GetNamedSecurityInfoW failed for {}", path.display()),
        ));
    }
    let _security_descriptor = LocalMem(security_descriptor);
    if dacl.is_null() {
        return Ok(None);
    }
    let mut info = ACL_SIZE_INFORMATION::default();
    let ok = unsafe {
        GetAclInformation(
            dacl.cast::<ACL>(),
            (&mut info as *mut ACL_SIZE_INFORMATION).cast::<c_void>(),
            std::mem::size_of::<ACL_SIZE_INFORMATION>() as u32,
            AclSizeInformation,
        )
    };
    if ok == 0 {
        return Err(last_os_error("GetAclInformation failed"));
    }
    let bytes =
        unsafe { std::slice::from_raw_parts(dacl.cast::<u8>(), info.AclBytesInUse as usize) };
    Ok(Some(bytes.to_vec()))
}

fn add_explicit_aces(path: &Path, entries: &[(PSID, u32, i32)]) -> io::Result<()> {
    let path_wide = to_wide(path.as_os_str());
    let mut security_descriptor: PSID = ptr::null_mut();
    let mut dacl = ptr::null_mut();
    let code = unsafe {
        GetNamedSecurityInfoW(
            path_wide.as_ptr(),
            1,
            DACL_SECURITY_INFORMATION,
            ptr::null_mut(),
            ptr::null_mut(),
            &mut dacl,
            ptr::null_mut(),
            &mut security_descriptor,
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            format!("GetNamedSecurityInfoW failed for {}", path.display()),
        ));
    }
    let _security_descriptor = LocalMem(security_descriptor);
    let inheritance = inheritance_for_path(path);
    let explicits = entries
        .iter()
        .map(|(psid, mask, mode)| explicit_access(*psid, *mask, *mode, inheritance))
        .collect::<Vec<_>>();
    let mut new_dacl = ptr::null_mut();
    let code = unsafe {
        SetEntriesInAclW(
            explicits.len() as u32,
            explicits.as_ptr(),
            dacl,
            &mut new_dacl,
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            format!("SetEntriesInAclW failed for {}", path.display()),
        ));
    }
    let _new_dacl = LocalMem(new_dacl);
    let code = unsafe {
        SetNamedSecurityInfoW(
            path_wide.as_ptr() as *mut u16,
            1,
            DACL_SECURITY_INFORMATION,
            ptr::null_mut(),
            ptr::null_mut(),
            new_dacl,
            ptr::null_mut(),
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            format!("SetNamedSecurityInfoW failed for {}", path.display()),
        ));
    }
    Ok(())
}

fn restore_acl_guards(guards: &[AclGuard]) {
    for guard in guards.iter().rev() {
        let _ = restore_acl_guard(guard);
    }
}

fn restore_acl_guard(guard: &AclGuard) -> io::Result<()> {
    if !guard.path.exists() {
        return Ok(());
    }
    let path_wide = to_wide(guard.path.as_os_str());
    let dacl = guard
        .original_dacl
        .as_ref()
        .map_or(ptr::null_mut(), |bytes| {
            bytes.as_ptr().cast::<ACL>().cast_mut()
        });
    let code = unsafe {
        SetNamedSecurityInfoW(
            path_wide.as_ptr() as *mut u16,
            1,
            DACL_SECURITY_INFORMATION,
            ptr::null_mut(),
            ptr::null_mut(),
            dacl,
            ptr::null_mut(),
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            format!("SetNamedSecurityInfoW failed for {}", guard.path.display()),
        ));
    }
    Ok(())
}

fn explicit_access(psid: PSID, mask: u32, mode: i32, inheritance: u32) -> EXPLICIT_ACCESS_W {
    EXPLICIT_ACCESS_W {
        grfAccessPermissions: mask,
        grfAccessMode: mode,
        grfInheritance: inheritance,
        Trustee: TRUSTEE_W {
            pMultipleTrustee: ptr::null_mut(),
            MultipleTrusteeOperation: 0,
            TrusteeForm: TRUSTEE_IS_SID,
            TrusteeType: TRUSTEE_IS_UNKNOWN,
            ptstrName: psid.cast::<u16>(),
        },
    }
}

fn inheritance_for_path(path: &Path) -> u32 {
    if path.is_dir() {
        CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE
    } else {
        0
    }
}

fn create_restricted_token(psid_capability: &LocalSid) -> io::Result<RestrictedToken> {
    let base = current_token_for_restriction()?;
    let mut logon_sid = logon_sid_bytes(base.raw())?;
    let psid_logon = logon_sid.as_mut_ptr().cast::<c_void>();
    let mut everyone_sid = world_sid_bytes()?;
    let psid_everyone = everyone_sid.as_mut_ptr().cast::<c_void>();
    let mut restricted_sids = vec![
        SID_AND_ATTRIBUTES {
            Sid: psid_capability.as_ptr(),
            Attributes: 0,
        },
        SID_AND_ATTRIBUTES {
            Sid: psid_logon,
            Attributes: 0,
        },
        SID_AND_ATTRIBUTES {
            Sid: psid_everyone,
            Attributes: 0,
        },
    ];
    let mut token = ptr::null_mut();
    let ok = unsafe {
        CreateRestrictedToken(
            base.raw(),
            DISABLE_MAX_PRIVILEGE | LUA_TOKEN | WRITE_RESTRICTED,
            0,
            ptr::null(),
            0,
            ptr::null(),
            restricted_sids.len() as u32,
            restricted_sids.as_mut_ptr(),
            &mut token,
        )
    };
    if ok == 0 {
        return Err(last_os_error("CreateRestrictedToken failed"));
    }
    let token = OwnedHandle(token);
    set_default_dacl(
        token.raw(),
        &[psid_capability.as_ptr(), psid_logon, psid_everyone],
    )?;
    enable_single_privilege(token.raw(), "SeChangeNotifyPrivilege")?;
    Ok(RestrictedToken {
        handle: token,
        logon_sid,
        everyone_sid,
    })
}

fn current_token_for_restriction() -> io::Result<OwnedHandle> {
    let desired = TOKEN_DUPLICATE
        | TOKEN_QUERY
        | TOKEN_ASSIGN_PRIMARY
        | TOKEN_ADJUST_DEFAULT
        | TOKEN_ADJUST_SESSIONID
        | TOKEN_ADJUST_PRIVILEGES;
    let mut token = ptr::null_mut();
    let ok = unsafe { OpenProcessToken(GetCurrentProcess(), desired, &mut token) };
    if ok == 0 {
        Err(last_os_error("OpenProcessToken failed"))
    } else {
        Ok(OwnedHandle(token))
    }
}

fn logon_sid_bytes(token: HANDLE) -> io::Result<Vec<u8>> {
    if let Some(sid) = scan_token_groups_for_logon(token) {
        return Ok(sid);
    }
    Err(io::Error::other("logon SID not present on token"))
}

fn scan_token_groups_for_logon(token: HANDLE) -> Option<Vec<u8>> {
    let mut needed = 0;
    unsafe {
        GetTokenInformation(token, TokenGroups, ptr::null_mut(), 0, &mut needed);
    }
    if needed == 0 {
        return None;
    }
    let mut buffer = vec![0u8; needed as usize];
    let ok = unsafe {
        GetTokenInformation(
            token,
            TokenGroups,
            buffer.as_mut_ptr().cast::<c_void>(),
            needed,
            &mut needed,
        )
    };
    if ok == 0 || needed as usize <= std::mem::size_of::<u32>() {
        return None;
    }
    let group_count = unsafe { ptr::read_unaligned(buffer.as_ptr().cast::<u32>()) as usize };
    let after_count = unsafe { buffer.as_ptr().add(std::mem::size_of::<u32>()) } as usize;
    let align = std::mem::align_of::<SID_AND_ATTRIBUTES>();
    let aligned = (after_count + (align - 1)) & !(align - 1);
    let groups = aligned as *const SID_AND_ATTRIBUTES;
    for index in 0..group_count {
        let entry = unsafe { ptr::read_unaligned(groups.add(index)) };
        if (entry.Attributes & SE_GROUP_LOGON_ID as u32) == SE_GROUP_LOGON_ID as u32 {
            let sid_len = unsafe { GetLengthSid(entry.Sid) };
            if sid_len == 0 {
                return None;
            }
            let mut out = vec![0u8; sid_len as usize];
            let ok = unsafe { CopySid(sid_len, out.as_mut_ptr().cast::<c_void>(), entry.Sid) };
            if ok == 0 {
                return None;
            }
            return Some(out);
        }
    }
    None
}

fn world_sid_bytes() -> io::Result<Vec<u8>> {
    let mut size = 0;
    unsafe {
        CreateWellKnownSid(WIN_WORLD_SID, ptr::null_mut(), ptr::null_mut(), &mut size);
    }
    if size == 0 {
        return Err(last_os_error("CreateWellKnownSid size query failed"));
    }
    let mut buffer = vec![0u8; size as usize];
    let ok = unsafe {
        CreateWellKnownSid(
            WIN_WORLD_SID,
            ptr::null_mut(),
            buffer.as_mut_ptr().cast::<c_void>(),
            &mut size,
        )
    };
    if ok == 0 {
        Err(last_os_error("CreateWellKnownSid failed"))
    } else {
        Ok(buffer)
    }
}

fn set_default_dacl(token: HANDLE, sids: &[PSID]) -> io::Result<()> {
    let entries = sids
        .iter()
        .map(|sid| explicit_access(*sid, GENERIC_ALL, SET_ACCESS, 0))
        .collect::<Vec<_>>();
    let mut new_dacl = ptr::null_mut();
    let code = unsafe {
        SetEntriesInAclW(
            entries.len() as u32,
            entries.as_ptr(),
            ptr::null(),
            &mut new_dacl,
        )
    };
    if code != ERROR_SUCCESS {
        return Err(win32_error(
            code,
            "SetEntriesInAclW failed for token default DACL",
        ));
    }
    let _new_dacl = LocalMem(new_dacl);
    let info = TOKEN_DEFAULT_DACL {
        DefaultDacl: new_dacl,
    };
    let ok = unsafe {
        SetTokenInformation(
            token,
            TokenDefaultDacl,
            (&info as *const TOKEN_DEFAULT_DACL).cast::<c_void>(),
            std::mem::size_of::<TOKEN_DEFAULT_DACL>() as u32,
        )
    };
    if ok == 0 {
        Err(last_os_error(
            "SetTokenInformation(TokenDefaultDacl) failed",
        ))
    } else {
        Ok(())
    }
}

fn enable_single_privilege(token: HANDLE, name: &str) -> io::Result<()> {
    let mut luid = LUID {
        LowPart: 0,
        HighPart: 0,
    };
    let name_wide = to_wide(name);
    let ok = unsafe { LookupPrivilegeValueW(ptr::null(), name_wide.as_ptr(), &mut luid) };
    if ok == 0 {
        return Err(last_os_error("LookupPrivilegeValueW failed"));
    }
    let privileges = TOKEN_PRIVILEGES {
        PrivilegeCount: 1,
        Privileges: [windows_sys::Win32::Security::LUID_AND_ATTRIBUTES {
            Luid: luid,
            Attributes: 0x0000_0002,
        }],
    };
    let ok = unsafe {
        AdjustTokenPrivileges(token, 0, &privileges, 0, ptr::null_mut(), ptr::null_mut())
    };
    if ok == 0 {
        Err(last_os_error("AdjustTokenPrivileges failed"))
    } else {
        Ok(())
    }
}

fn create_kill_on_close_job(process: HANDLE) -> io::Result<HANDLE> {
    let job = unsafe { CreateJobObjectW(ptr::null(), ptr::null()) };
    if is_invalid_handle(job) {
        return Err(last_os_error("CreateJobObjectW failed"));
    }
    let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
    limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    let ok = unsafe {
        SetInformationJobObject(
            job,
            JobObjectExtendedLimitInformation,
            (&limits as *const JOBOBJECT_EXTENDED_LIMIT_INFORMATION).cast::<c_void>(),
            std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
        )
    };
    if ok == 0 {
        unsafe {
            CloseHandle(job);
        }
        return Err(last_os_error("SetInformationJobObject failed"));
    }
    let ok = unsafe { AssignProcessToJobObject(job, process) };
    if ok == 0 {
        unsafe {
            CloseHandle(job);
        }
        return Err(last_os_error("AssignProcessToJobObject failed"));
    }
    Ok(job)
}

impl RestrictedWindowsChild {
    fn id(&self) -> u32 {
        self.process_id
    }

    fn kill(&mut self) -> io::Result<()> {
        if !is_invalid_handle(self.job) {
            let ok = unsafe { TerminateJobObject(self.job, 1) };
            if ok != 0 {
                return Ok(());
            }
        }
        let ok = unsafe { TerminateProcess(self.process, 1) };
        if ok == 0 {
            Err(last_os_error("TerminateProcess failed"))
        } else {
            Ok(())
        }
    }

    fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
        if let Some(status) = self.exit_status {
            return Ok(Some(status));
        }
        match unsafe { WaitForSingleObject(self.process, 0) } {
            WAIT_TIMEOUT => Ok(None),
            WAIT_OBJECT_0 => {
                let status = self.finish_after_exit()?;
                Ok(Some(status))
            }
            WAIT_FAILED => Err(last_os_error("WaitForSingleObject failed")),
            other => Err(io::Error::other(format!("unexpected wait result {other}"))),
        }
    }

    fn wait(&mut self) -> io::Result<ExitStatus> {
        if let Some(status) = self.exit_status {
            return Ok(status);
        }
        match unsafe { WaitForSingleObject(self.process, INFINITE) } {
            WAIT_OBJECT_0 => self.finish_after_exit(),
            WAIT_FAILED => Err(last_os_error("WaitForSingleObject failed")),
            other => Err(io::Error::other(format!("unexpected wait result {other}"))),
        }
    }

    fn finish_after_exit(&mut self) -> io::Result<ExitStatus> {
        let mut code = 1;
        let ok = unsafe { GetExitCodeProcess(self.process, &mut code) };
        if ok == 0 {
            return Err(last_os_error("GetExitCodeProcess failed"));
        }
        self.close_thread_handle();
        self.cleanup_acl_guards();
        let status = ExitStatus::from_raw(code);
        self.exit_status = Some(status);
        Ok(status)
    }

    fn close_thread_handle(&mut self) {
        if !is_invalid_handle(self.thread) {
            unsafe {
                CloseHandle(self.thread);
            }
            self.thread = ptr::null_mut();
        }
    }

    fn cleanup_acl_guards(&mut self) {
        if self.acl_cleaned {
            return;
        }
        restore_acl_guards(&self.acl_guards);
        self.acl_cleaned = true;
    }
}

impl Drop for RestrictedWindowsChild {
    fn drop(&mut self) {
        self.close_thread_handle();
        if self.exit_status.is_none() && !is_invalid_handle(self.job) {
            unsafe {
                CloseHandle(self.job);
            }
            self.job = ptr::null_mut();
            let _ = unsafe { WaitForSingleObject(self.process, 5000) };
        }
        self.cleanup_acl_guards();
        if !is_invalid_handle(self.process) {
            unsafe {
                CloseHandle(self.process);
            }
            self.process = ptr::null_mut();
        }
        if !is_invalid_handle(self.job) {
            unsafe {
                CloseHandle(self.job);
            }
            self.job = ptr::null_mut();
        }
    }
}

impl LocalSid {
    fn from_string(sid: &str) -> io::Result<Self> {
        let wide = to_wide(sid);
        let mut ptr = ptr::null_mut();
        let ok = unsafe { ConvertStringSidToSidW(wide.as_ptr(), &mut ptr) };
        if ok == 0 {
            Err(last_os_error("ConvertStringSidToSidW failed"))
        } else {
            Ok(Self { ptr })
        }
    }

    fn as_ptr(&self) -> PSID {
        self.ptr
    }
}

impl Drop for LocalSid {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe {
                LocalFree(self.ptr.cast::<c_void>());
            }
            self.ptr = ptr::null_mut();
        }
    }
}

impl<T> Drop for LocalMem<T> {
    fn drop(&mut self) {
        if !self.0.is_null() {
            unsafe {
                LocalFree(self.0.cast::<c_void>());
            }
            self.0 = ptr::null_mut();
        }
    }
}

impl OwnedHandle {
    fn raw(&self) -> HANDLE {
        self.0
    }

    fn into_raw(mut self) -> HANDLE {
        let handle = self.0;
        self.0 = ptr::null_mut();
        handle
    }
}

impl Drop for OwnedHandle {
    fn drop(&mut self) {
        if !is_invalid_handle(self.0) {
            unsafe {
                CloseHandle(self.0);
            }
            self.0 = ptr::null_mut();
        }
    }
}

impl StartupHandles {
    fn new(options: SandboxProcessOptions) -> io::Result<Self> {
        let mut owned = Vec::new();
        let stdin = stdio_handle(options.stdin, STD_INPUT_HANDLE, true, &mut owned)?;
        let stdout = stdio_handle(options.stdout, STD_OUTPUT_HANDLE, false, &mut owned)?;
        let stderr = stdio_handle(options.stderr, STD_ERROR_HANDLE, false, &mut owned)?;
        Ok(Self {
            stdin,
            stdout,
            stderr,
            _owned: owned,
        })
    }

    fn new_with_parent_pipes(
        options: SandboxProcessOptions,
    ) -> io::Result<(Self, ParentPipeHandles)> {
        let mut owned = Vec::new();
        let mut parent = ParentPipeHandles::default();
        let stdin = match options.stdin {
            SandboxStdio::Piped => {
                let (child_read, parent_write) = create_pipe_pair("stdin")?;
                set_handle_inherit(child_read.raw(), true)?;
                set_handle_inherit(parent_write.raw(), false)?;
                let child_handle = child_read.raw();
                owned.push(child_read);
                parent.stdin = Some(parent_write);
                child_handle
            }
            mode => stdio_handle(mode, STD_INPUT_HANDLE, true, &mut owned)?,
        };
        let stdout = match options.stdout {
            SandboxStdio::Piped => {
                let (parent_read, child_write) = create_pipe_pair("stdout")?;
                set_handle_inherit(child_write.raw(), true)?;
                set_handle_inherit(parent_read.raw(), false)?;
                let child_handle = child_write.raw();
                owned.push(child_write);
                parent.stdout = Some(parent_read);
                child_handle
            }
            mode => stdio_handle(mode, STD_OUTPUT_HANDLE, false, &mut owned)?,
        };
        let stderr = match options.stderr {
            SandboxStdio::Piped => {
                let (parent_read, child_write) = create_pipe_pair("stderr")?;
                set_handle_inherit(child_write.raw(), true)?;
                set_handle_inherit(parent_read.raw(), false)?;
                let child_handle = child_write.raw();
                owned.push(child_write);
                parent.stderr = Some(parent_read);
                child_handle
            }
            mode => stdio_handle(mode, STD_ERROR_HANDLE, false, &mut owned)?,
        };
        Ok((
            Self {
                stdin,
                stdout,
                stderr,
                _owned: owned,
            },
            parent,
        ))
    }
}

impl ParentPipeHandles {
    fn into_async_files(
        self,
    ) -> io::Result<(
        Option<tokio::fs::File>,
        Option<tokio::fs::File>,
        Option<tokio::fs::File>,
    )> {
        Ok((
            owned_handle_to_async_file(self.stdin),
            owned_handle_to_async_file(self.stdout),
            owned_handle_to_async_file(self.stderr),
        ))
    }
}

fn owned_handle_to_async_file(handle: Option<OwnedHandle>) -> Option<tokio::fs::File> {
    let handle = handle?;
    let raw = handle.into_raw();
    Some(unsafe { tokio::fs::File::from_raw_handle(raw as RawHandle) })
}

fn startup_info(handles: &StartupHandles) -> STARTUPINFOW {
    STARTUPINFOW {
        cb: std::mem::size_of::<STARTUPINFOW>() as u32,
        dwFlags: STARTF_USESTDHANDLES,
        hStdInput: handles.stdin,
        hStdOutput: handles.stdout,
        hStdError: handles.stderr,
        ..Default::default()
    }
}

fn stdio_handle(
    mode: SandboxStdio,
    standard_handle: u32,
    read_access: bool,
    owned: &mut Vec<OwnedHandle>,
) -> io::Result<HANDLE> {
    let handle = match mode {
        SandboxStdio::Inherit => {
            let inherited = unsafe { GetStdHandle(standard_handle) };
            if is_invalid_handle(inherited) {
                open_nul(read_access, owned)?
            } else {
                inherited
            }
        }
        SandboxStdio::Null => open_nul(read_access, owned)?,
        SandboxStdio::Piped => {
            return Err(io::Error::other(
                "Windows restricted sandbox does not support piped stdio yet",
            ));
        }
    };
    set_handle_inherit(handle, true)?;
    Ok(handle)
}

fn create_pipe_pair(label: &str) -> io::Result<(OwnedHandle, OwnedHandle)> {
    let mut read: HANDLE = ptr::null_mut();
    let mut write: HANDLE = ptr::null_mut();
    let ok = unsafe { CreatePipe(&mut read, &mut write, ptr::null_mut(), 0) };
    if ok == 0 {
        return Err(last_os_error(&format!("CreatePipe({label}) failed")));
    }
    Ok((OwnedHandle(read), OwnedHandle(write)))
}

fn set_handle_inherit(handle: HANDLE, inherit: bool) -> io::Result<()> {
    let flags = if inherit { HANDLE_FLAG_INHERIT } else { 0 };
    let ok = unsafe { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, flags) };
    if ok == 0 {
        Err(last_os_error("SetHandleInformation failed"))
    } else {
        Ok(())
    }
}

fn open_nul(read_access: bool, owned: &mut Vec<OwnedHandle>) -> io::Result<HANDLE> {
    let name = to_wide("NUL");
    let access = if read_access {
        FILE_GENERIC_READ
    } else {
        FILE_GENERIC_WRITE
    };
    let handle = unsafe {
        CreateFileW(
            name.as_ptr(),
            access,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            ptr::null(),
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
            ptr::null_mut(),
        )
    };
    if is_invalid_handle(handle) {
        Err(last_os_error("CreateFileW(NUL) failed"))
    } else {
        owned.push(OwnedHandle(handle));
        Ok(handle)
    }
}

fn command_argv(program: &Path, args: Vec<String>) -> Vec<String> {
    let mut argv = Vec::with_capacity(args.len() + 1);
    argv.push(program.to_string_lossy().into_owned());
    argv.extend(args);
    argv
}

fn environment_block(policy: &RuntimeSandboxPolicy) -> Vec<u16> {
    let mut env = BTreeMap::<String, String>::new();
    for (name, value) in std::env::vars_os() {
        let name = name.to_string_lossy().into_owned();
        if policy.is_env_var_protected(&name) {
            continue;
        }
        env.insert(name, value.to_string_lossy().into_owned());
    }

    let mut block = Vec::new();
    for (name, value) in env {
        let mut item = to_wide(format!("{name}={value}"));
        item.pop();
        block.extend(item);
        block.push(0);
    }
    block.push(0);
    block
}

fn random_capability_sid() -> String {
    let uuid = Uuid::new_v4();
    let bytes = uuid.as_bytes();
    let a = u32::from_le_bytes(bytes[0..4].try_into().expect("uuid field"));
    let b = u32::from_le_bytes(bytes[4..8].try_into().expect("uuid field"));
    let c = u32::from_le_bytes(bytes[8..12].try_into().expect("uuid field"));
    let d = u32::from_le_bytes(bytes[12..16].try_into().expect("uuid field"));
    format!("S-1-5-21-{a}-{b}-{c}-{d}")
}

fn argv_to_command_line(argv: &[String]) -> String {
    argv.iter()
        .map(|arg| quote_windows_arg(arg))
        .collect::<Vec<_>>()
        .join(" ")
}

fn quote_windows_arg(arg: &str) -> String {
    let needs_quotes = arg.is_empty()
        || arg
            .chars()
            .any(|ch| matches!(ch, ' ' | '\t' | '\n' | '\r' | '"'));
    if !needs_quotes {
        return arg.to_string();
    }

    let mut quoted = String::with_capacity(arg.len() + 2);
    quoted.push('"');
    let mut backslashes = 0;
    for ch in arg.chars() {
        match ch {
            '\\' => backslashes += 1,
            '"' => {
                quoted.push_str(&"\\".repeat(backslashes * 2 + 1));
                quoted.push('"');
                backslashes = 0;
            }
            _ => {
                if backslashes > 0 {
                    quoted.push_str(&"\\".repeat(backslashes));
                    backslashes = 0;
                }
                quoted.push(ch);
            }
        }
    }
    if backslashes > 0 {
        quoted.push_str(&"\\".repeat(backslashes * 2));
    }
    quoted.push('"');
    quoted
}

fn to_wide<S: AsRef<OsStr>>(value: S) -> Vec<u16> {
    let mut wide = value.as_ref().encode_wide().collect::<Vec<_>>();
    wide.push(0);
    wide
}

fn is_invalid_handle(handle: HANDLE) -> bool {
    handle.is_null() || handle == INVALID_HANDLE_VALUE
}

fn last_os_error(context: &str) -> io::Error {
    let err = unsafe { GetLastError() };
    io::Error::other(format!(
        "{context}: {}",
        io::Error::from_raw_os_error(err as i32)
    ))
}

fn win32_error(code: u32, context: impl Into<String>) -> io::Error {
    let message = format!(
        "{}: {}",
        context.into(),
        io::Error::from_raw_os_error(code as i32)
    );
    if code == ERROR_ACCESS_DENIED {
        io::Error::new(io::ErrorKind::PermissionDenied, message)
    } else {
        io::Error::other(message)
    }
}
