//
// Syd: rock-solid application kernel
// src/err.rs: Error types and error handling code
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

// SAFETY: This module has been liberated from unsafe code!
#![forbid(unsafe_code)]

use std::{
    array::TryFromSliceError,
    env::VarError,
    error::Error,
    fmt, io,
    net::AddrParseError,
    num::{ParseIntError, TryFromIntError},
    str::Utf8Error,
    thread::JoinHandle,
};

use btoi::ParseIntegerError;
#[cfg(feature = "oci")]
use libcgroups::common::AnyManagerError;
#[cfg(feature = "oci")]
use libcgroups::common::CreateCgroupSetupError;
#[cfg(feature = "oci")]
use libcontainer::error::LibcontainerError;
#[cfg(feature = "oci")]
use libcontainer::signal::SignalError;
#[cfg(feature = "oci")]
use libcontainer::utils::PathBufExtError;
use libseccomp::error::SeccompError;
use nix::errno::Errno;
use procfs_core::ProcError;
use shellexpand::LookupError;
#[cfg(feature = "oci")]
use tracing::subscriber::SetGlobalDefaultError;

use crate::{caps::errors::CapsError, elf::ElfError, landlock::RulesetError};

/// Convenience type to use for functions returning a SydError.
pub type SydResult<T> = std::result::Result<T, SydError>;

/// Convenience type to use for join handlers returning a SydError.
pub type SydJoinHandle<T> = JoinHandle<SydResult<T>>;

/// A macro to create a SydError from the last errno.
#[macro_export]
macro_rules! lasterrno {
    () => {
        SydError::Nix(nix::errno::Errno::last())
    };
}

/// Enum representing different types of errors
pub enum SydError {
    /// This error represents a network address parse error.
    Addr(AddrParseError),
    /// This error type represents a lexopt error.
    Args(lexopt::Error),
    /// This error type represents a capability error.
    Caps(CapsError),
    /// This error type represents an ELF parse error.
    Elf(ElfError),
    /// This error type represents an error in environment variable lookup in configuration.
    Env(LookupError<VarError>),
    /// This error type represents an `Errno`.
    Nix(Errno),
    /// This error type represents JSON errors.
    Json(serde_json::Error),
    /// This error type represents integer parse errors.
    ParseInt(ParseIntError),
    /// This error type represents integer parse errors.
    ParseInteger(ParseIntegerError),
    /// This error type represents integer parse errors.
    TryInt(TryFromIntError),
    /// This error type represents a slice conversion error.
    TrySlice(TryFromSliceError),
    /// This error type represents size parse errors.
    ParseSize(parse_size::Error),
    /// This error type represents a /proc filesystem error.
    Proc(ProcError),
    /// This error type represents Seccomp errors.
    Scmp(SeccompError),
    /// This error type represents UTF-8 errors.
    Utf8(Utf8Error),
    #[cfg(feature = "oci")]
    /// This error type represents a Cgroup setup error.
    CgSetup(CreateCgroupSetupError),
    #[cfg(feature = "oci")]
    /// This error type represents a miscellaneous Cgroup error.
    CgMisc(AnyManagerError),
    #[cfg(feature = "oci")]
    /// This error type represents container errors.
    Cont(LibcontainerError),
    #[cfg(feature = "oci")]
    /// This error type represents a path canonicalization error.
    Pext(PathBufExtError),
    #[cfg(feature = "oci")]
    /// This error type represents a tracing subscriber error.
    SetTracing(SetGlobalDefaultError),
    #[cfg(feature = "oci")]
    /// This error type represents a signal parsing error.
    Signal(SignalError<String>),
    #[cfg(feature = "oci")]
    /// This error type represents OCI-Spec errors.
    Spec(oci_spec::OciSpecError),
}

impl SydError {
    /// Converts a SydError to an Errno if applicable.
    pub fn errno(&self) -> Option<Errno> {
        match self {
            Self::Nix(errno) => Some(*errno),
            Self::Proc(error) => proc_error_to_errno(error),
            Self::Scmp(error) => scmp2no(error),
            Self::ParseInt(_) | Self::ParseInteger(_) | Self::ParseSize(_) => Some(Errno::EINVAL),
            _ => None,
        }
    }
}

impl fmt::Debug for SydError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Addr(error) => write!(f, "AddrParseError: {error:?}"),
            Self::Args(error) => write!(f, "ArgsParseError: {error:?}"),
            Self::Caps(error) => write!(f, "CapsError: {error:?}"),
            Self::Elf(error) => write!(f, "ElfError: {error:?}"),
            Self::Env(error) => write!(f, "LookupError<VarError>: {error:?}"),
            Self::Nix(errno) => write!(f, "LinuxError: {errno:?}"),
            Self::Json(error) => write!(f, "JsonError: {error:?}"),
            Self::ParseInt(error) => write!(f, "ParseIntError: {error:?}"),
            Self::ParseInteger(error) => write!(f, "ParseIntegerError: {error:?}"),
            Self::Scmp(error) => write!(f, "SeccompError: {error:?}"),
            Self::TryInt(error) => write!(f, "TryFromIntError: {error:?}"),
            Self::TrySlice(error) => write!(f, "TryFromSliceError: {error:?}"),
            Self::ParseSize(error) => write!(f, "ParseSizeError: {error:?}"),
            Self::Proc(error) => write!(f, "ProcError: {error:?}"),
            Self::Utf8(error) => write!(f, "Utf8Error: {error:?}"),
            #[cfg(feature = "oci")]
            Self::CgSetup(error) => write!(f, "CgroupSetupError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::CgMisc(error) => write!(f, "AnyManagerError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::Cont(error) => write!(f, "ContainerError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::Pext(error) => write!(f, "PathBufExtError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::SetTracing(error) => write!(f, "SetGlobalDefaultError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::Signal(error) => write!(f, "SignalError: {error:?}"),
            #[cfg(feature = "oci")]
            Self::Spec(error) => write!(f, "OciSpecError: {error:?}"),
        }
    }
}

impl fmt::Display for SydError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Addr(error) => write!(f, "AddrParseError: {error}"),
            Self::Args(error) => write!(f, "ArgsParseError: {error}"),
            Self::Caps(error) => write!(f, "CapsError: {error}"),
            Self::Elf(error) => write!(f, "ElfError: {error}"),
            Self::Env(error) => write!(f, "LookupError<VarError>: {error}"),
            Self::Nix(errno) => write!(f, "LinuxError: {errno}"),
            Self::Json(error) => write!(f, "JsonError: {error}"),
            Self::ParseInt(error) => write!(f, "ParseIntError: {error}"),
            Self::ParseInteger(error) => write!(f, "ParseIntegerError: {error}"),
            Self::Scmp(error) => write!(f, "SeccompError: {error}"),
            Self::TryInt(error) => write!(f, "TryFromIntError: {error}"),
            Self::TrySlice(error) => write!(f, "TryFromSliceError: {error}"),
            Self::ParseSize(error) => write!(f, "ParseSizeError: {error}"),
            Self::Proc(error) => write!(f, "ProcError: {error}"),
            Self::Utf8(error) => write!(f, "Utf8Error: {error}"),
            #[cfg(feature = "oci")]
            Self::CgSetup(error) => write!(f, "CgroupSetupError: {error}"),
            #[cfg(feature = "oci")]
            Self::CgMisc(error) => write!(f, "AnyManagerError: {error}"),
            #[cfg(feature = "oci")]
            Self::Cont(error) => write!(f, "ContainerError: {error}"),
            #[cfg(feature = "oci")]
            Self::Pext(error) => write!(f, "PathBufExtError: {error}"),
            #[cfg(feature = "oci")]
            Self::SetTracing(error) => write!(f, "SetGlobalDefaultError: {error}"),
            #[cfg(feature = "oci")]
            Self::Signal(error) => write!(f, "SignalError: {error}"),
            #[cfg(feature = "oci")]
            Self::Spec(error) => write!(f, "OciSpecError: {error}"),
        }
    }
}

impl std::error::Error for SydError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Addr(error) => Some(error),
            Self::Args(error) => Some(error),
            Self::Nix(errno) => Some(errno),
            Self::ParseInt(error) => Some(error),
            Self::ParseInteger(error) => Some(error),
            Self::TryInt(error) => Some(error),
            Self::TrySlice(error) => Some(error),
            // TODO: Change to Some(error) when
            // parse-size is upgraded, see Cargo.toml
            Self::ParseSize(_error) => None,
            Self::Proc(error) => Some(error),
            Self::Utf8(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::CgSetup(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::CgMisc(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::Cont(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::Pext(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::SetTracing(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::Signal(error) => Some(error),
            #[cfg(feature = "oci")]
            Self::Spec(error) => Some(error),
            _ => None,
        }
    }
}

// Conversions from std::io::Error to SydError.
impl From<io::Error> for SydError {
    fn from(err: io::Error) -> SydError {
        SydError::Nix(err2no(&err))
    }
}

// Conversions from AddrParseError to SydError.
impl From<AddrParseError> for SydError {
    fn from(err: AddrParseError) -> SydError {
        SydError::Addr(err)
    }
}

// Conversions from Utf8Error to SydError.
impl From<Utf8Error> for SydError {
    fn from(err: Utf8Error) -> SydError {
        Self::Utf8(err)
    }
}

// Conversions from ProcError to SydError.
impl From<ProcError> for SydError {
    fn from(err: ProcError) -> SydError {
        Self::Proc(err)
    }
}

// Conversions from lexopt::Error to SydError.
impl From<lexopt::Error> for SydError {
    fn from(err: lexopt::Error) -> SydError {
        Self::Args(err)
    }
}

// Conversions from CapsError to SydError.
impl From<CapsError> for SydError {
    fn from(err: CapsError) -> SydError {
        Self::Caps(err)
    }
}

// Conversions from ElfError to SydError.
impl From<ElfError> for SydError {
    fn from(err: ElfError) -> SydError {
        Self::Elf(err)
    }
}

// Conversions from LookupError<VarError> to SydError.
impl From<LookupError<VarError>> for SydError {
    fn from(err: LookupError<VarError>) -> SydError {
        Self::Env(err)
    }
}

// Conversions from nix::errno::Errno to SydError.
impl From<Errno> for SydError {
    fn from(err: Errno) -> SydError {
        Self::Nix(err)
    }
}

// Conversions from serde_json::Error to SydError.
impl From<serde_json::Error> for SydError {
    fn from(err: serde_json::Error) -> SydError {
        Self::Json(err)
    }
}

// Conversions from AnyManagerError to SydError.
#[cfg(feature = "oci")]
impl From<AnyManagerError> for SydError {
    fn from(err: AnyManagerError) -> SydError {
        Self::CgMisc(err)
    }
}

// Conversions from CreateCgroupSetupError to SydError.
#[cfg(feature = "oci")]
impl From<CreateCgroupSetupError> for SydError {
    fn from(err: CreateCgroupSetupError) -> SydError {
        Self::CgSetup(err)
    }
}

// Conversions from LibcontainerError to SydError.
#[cfg(feature = "oci")]
impl From<LibcontainerError> for SydError {
    fn from(err: LibcontainerError) -> SydError {
        Self::Cont(err)
    }
}

// Conversions from PathBufExtError to SydError.
#[cfg(feature = "oci")]
impl From<PathBufExtError> for SydError {
    fn from(err: PathBufExtError) -> SydError {
        Self::Pext(err)
    }
}

// Conversions from SetGlobalDefaultError to SydError.
#[cfg(feature = "oci")]
impl From<SetGlobalDefaultError> for SydError {
    fn from(err: SetGlobalDefaultError) -> SydError {
        Self::SetTracing(err)
    }
}

// Conversions from SignalError<String> to SydError.
#[cfg(feature = "oci")]
impl From<SignalError<String>> for SydError {
    fn from(err: SignalError<String>) -> SydError {
        Self::Signal(err)
    }
}

// Conversions from OciSpecError to SydError.
#[cfg(feature = "oci")]
impl From<oci_spec::OciSpecError> for SydError {
    fn from(err: oci_spec::OciSpecError) -> SydError {
        Self::Spec(err)
    }
}

// Conversions from ParseIntError to SydError.
impl From<ParseIntError> for SydError {
    fn from(err: ParseIntError) -> SydError {
        Self::ParseInt(err)
    }
}

// Conversions from ParseIntegerError to SydError.
impl From<ParseIntegerError> for SydError {
    fn from(err: ParseIntegerError) -> SydError {
        Self::ParseInteger(err)
    }
}

// Conversions from TryFromIntError to SydError.
impl From<TryFromIntError> for SydError {
    fn from(err: TryFromIntError) -> SydError {
        Self::TryInt(err)
    }
}

// Conversions from TryFromSliceError to SydError.
impl From<TryFromSliceError> for SydError {
    fn from(err: TryFromSliceError) -> SydError {
        Self::TrySlice(err)
    }
}

// Conversions from parse_size::Error to SydError.
impl From<parse_size::Error> for SydError {
    fn from(err: parse_size::Error) -> SydError {
        Self::ParseSize(err)
    }
}

// Conversions from SeccompError to SydError.
impl From<SeccompError> for SydError {
    fn from(err: SeccompError) -> SydError {
        Self::Scmp(err)
    }
}

/// Convert a std::io::Error into a nix::Errno.
pub fn err2no(err: &std::io::Error) -> Errno {
    err.raw_os_error()
        .map(Errno::from_raw)
        .unwrap_or(Errno::ENOSYS)
}

/// Convert a RulesetError into a nix::Errno.
pub fn err2set(err: &RulesetError) -> Errno {
    err.source()
        .and_then(|s| s.downcast_ref::<std::io::Error>())
        .map(err2no)
        .unwrap_or(Errno::EINVAL)
}

/// Convert SeccompError to a nix::Errno.
pub fn scmp2no(err: &SeccompError) -> Option<Errno> {
    err.sysrawrc().map(|errno| errno.abs()).map(Errno::from_raw)
}

/// Convert proc errno to nix errno.
pub fn proc_error_to_errno(error: &ProcError) -> Option<Errno> {
    match error {
        ProcError::PermissionDenied(_) => Some(Errno::EACCES),
        ProcError::NotFound(_) => Some(Errno::ESRCH),
        ProcError::Io(error, _) => Some(err2no(error)),
        ProcError::Other(_) => None,
        ProcError::Incomplete(_) => None,
        ProcError::InternalError(_) => None,
    }
}
