use std::{error, ffi::{self, CStr}, fmt, io, num::TryFromIntError, str};

#[derive(Debug)]
pub enum Error {
    Io(io::Error),
    IncorrectPassword,
    InvalidPassword,
    IntCast,
    PermissionDenied,
    Nul(ffi::NulError),
    Utf8(str::Utf8Error),
    Other(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Io(e) => write!(f, "{e}"),
            Self::IncorrectPassword => write!(f, "incorrect password"),
            Self::InvalidPassword => write!(f, "invalid password"),
            Self::IntCast => write!(f, "integer cast error"),
            Self::PermissionDenied => write!(f, "permission denied"),
            Self::Nul(e) => write!(f, "{e}"),
            Self::Utf8(e) => write!(f, "{e}"),
            Self::Other(e) => write!(f, "{e}"),
        }
    }
}

impl error::Error for Error {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Self::Io(e) => Some(e),
            Self::Nul(e) => Some(e),
            Self::Utf8(e) => Some(e),
            _ => None,
        }
    }
}

impl From<TryFromIntError> for Error {
    fn from(_value: TryFromIntError) -> Self {
        Self::IntCast
    }
}

impl From<io::Error> for Error {
    fn from(value: io::Error) -> Self {
        Self::Io(value)
    }
}

impl From<ffi::NulError> for Error {
    fn from(value: ffi::NulError) -> Self {
        Self::Nul(value)
    }
}

impl From<str::Utf8Error> for Error {
    fn from(value: str::Utf8Error) -> Self {
        Self::Utf8(value)
    }
}

impl From<&str> for Error {
    fn from(value: &str) -> Self {
        Self::Other(value.to_string())
    }
}

impl From<String> for Error {
    fn from(value: String) -> Self {
        Self::Other(value)
    }
}

pub fn get_group_names() -> Result<Vec<String>, Error> {
    let mut gids: Vec<libc::gid_t> = vec![0; 1];
    unsafe {
        let num = libc::getgroups(0, gids.as_mut_ptr());
        gids = vec![0; num.try_into()?];
        libc::getgroups(num, gids.as_mut_ptr());
    }
    let mut names = vec![];
    for id in gids {
        let name = unsafe {
            let gr = libc::getgrgid(id);
            if gr.is_null() {
                id.to_string()
            } else {
                let name = (*gr).gr_name;
                // if we don't take ownership here the OS can (and will)
                // reuse the memory
                CStr::from_ptr(name).to_str()?.to_string()
            }
        };
        names.push(name);
    }
    Ok(names)
}
