use std::path::Path;
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Account {
    pub username: String,
    pub password: String,
}
pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> {
    impl_::is_path_owned_by_current_user(path)
}
#[cfg(not(windows))]
mod impl_ {
    use std::path::Path;
    pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> {
        fn owner_from_path(path: &Path) -> std::io::Result<u32> {
            use std::os::unix::fs::MetadataExt;
            let meta = std::fs::symlink_metadata(path)?;
            Ok(meta.uid())
        }
        fn owner_of_current_process() -> std::io::Result<u32> {
            #[allow(unsafe_code)]
            let uid = unsafe { libc::geteuid() };
            Ok(uid)
        }
        use std::str::FromStr;
        let owner_of_path = owner_from_path(path)?;
        let owner_of_process = owner_of_current_process()?;
        if owner_of_path == owner_of_process {
            Ok(true)
        } else if let Some(sudo_uid) =
            std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok()))
        {
            Ok(owner_of_path == sudo_uid)
        } else {
            Ok(false)
        }
    }
}
#[cfg(windows)]
mod impl_ {
    use std::{
        io,
        mem::MaybeUninit,
        os::windows::io::{FromRawHandle as _, OwnedHandle},
        path::Path,
        ptr,
    };
    macro_rules! error {
        ($msg:expr) => {{
            let inner = io::Error::last_os_error();
            error!(inner, $msg);
        }};
        ($inner:expr, $msg:expr) => {{
            return Err(io::Error::new($inner.kind(), $msg));
        }};
    }
    pub fn is_path_owned_by_current_user(path: &Path) -> io::Result<bool> {
        use windows_sys::Win32::{
            Foundation::{GetLastError, LocalFree, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS},
            Security::{
                Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT},
                CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner,
                WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER,
                TOKEN_QUERY,
            },
            System::Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken},
        };
        if !path.exists() {
            return Err(io::Error::new(
                io::ErrorKind::NotFound,
                format!("{path:?} does not exist."),
            ));
        }
        if gix_path::realpath(path).ok() == gix_path::env::home_dir() {
            return Ok(true);
        }
        #[allow(unsafe_code)]
        unsafe {
            let (folder_owner, descriptor) = {
                let mut folder_owner = MaybeUninit::uninit();
                let mut pdescriptor = MaybeUninit::uninit();
                let result = GetNamedSecurityInfoW(
                    to_wide_path(path).as_ptr(),
                    SE_FILE_OBJECT,
                    OWNER_SECURITY_INFORMATION,
                    folder_owner.as_mut_ptr(),
                    ptr::null_mut(),
                    ptr::null_mut(),
                    ptr::null_mut(),
                    pdescriptor.as_mut_ptr(),
                );
                if result != ERROR_SUCCESS {
                    let inner = io::Error::from_raw_os_error(result as _);
                    error!(
                        inner,
                        format!(
                            "Couldn't get security information for path '{}' with err {inner}",
                            path.display()
                        )
                    );
                }
                (folder_owner.assume_init(), pdescriptor.assume_init())
            };
            struct Descriptor(PSECURITY_DESCRIPTOR);
            impl Drop for Descriptor {
                fn drop(&mut self) {
                    #[allow(unsafe_code)]
                    unsafe {
                        LocalFree(self.0 as _);
                    }
                }
            }
            let _descriptor = Descriptor(descriptor);
            let token = {
                let mut token = MaybeUninit::uninit();
                if OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, token.as_mut_ptr()) == 0
                    && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token.as_mut_ptr()) == 0
                {
                    error!("Couldn't acquire thread or process token");
                }
                token.assume_init()
            };
            let _owned_token = OwnedHandle::from_raw_handle(token as _);
            let buf = 'token_buf: {
                let mut buffer_size = 36;
                let mut heap_buf = vec![0; 36];
                loop {
                    if GetTokenInformation(
                        token,
                        TokenOwner,
                        heap_buf.as_mut_ptr().cast(),
                        heap_buf.len() as _,
                        &mut buffer_size,
                    ) != 0
                    {
                        break 'token_buf heap_buf;
                    }
                    if GetLastError() != ERROR_INSUFFICIENT_BUFFER {
                        error!("Couldn't acquire token ownership");
                    }
                    heap_buf.resize(buffer_size as _, 0);
                }
            };
            let token_owner = (*buf.as_ptr().cast::<TOKEN_OWNER>()).Owner;
            if EqualSid(folder_owner, token_owner) != 0 {
                return Ok(true);
            }
            if IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid) == 0 {
                return Ok(false);
            }
            let mut is_member = 0;
            if CheckTokenMembership(0, token_owner, &mut is_member) == 0 {
                error!("Couldn't check if user is an administrator");
            }
            Ok(is_member != 0)
        }
    }
    fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> {
        use std::os::windows::ffi::OsStrExt;
        let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect();
        wide_path.push(0);
        wide_path
    }
}