//! Error types for netlink operations.

use std::io;

use crate::util::addr::AddrError;
use crate::util::ifname::IfError;
use crate::util::parse::ParseError;

/// Result type for netlink operations.
pub type Result<T> = std::result::Result<T, Error>;

/// Errors that can occur during netlink operations.
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// I/O error from socket operations.
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),

    /// JSON serialization error.
    #[cfg(feature = "output")]
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    /// Kernel returned an error code.
    #[error("kernel error: {message} (errno {errno})")]
    Kernel {
        /// The errno value from the kernel.
        errno: i32,
        /// Human-readable error message.
        message: String,
    },

    /// Kernel error with operation context.
    #[error("{operation}: {message} (errno {errno})")]
    KernelWithContext {
        /// The operation that failed.
        operation: String,
        /// The errno value from the kernel.
        errno: i32,
        /// Human-readable error message.
        message: String,
    },

    /// Message was truncated.
    #[error("message truncated: expected {expected} bytes, got {actual}")]
    Truncated {
        /// Expected message length.
        expected: usize,
        /// Actual bytes received.
        actual: usize,
    },

    /// Invalid message format.
    #[error("invalid message: {0}")]
    InvalidMessage(String),

    /// Invalid attribute format.
    #[error("invalid attribute: {0}")]
    InvalidAttribute(String),

    /// Sequence number mismatch.
    #[error("sequence mismatch: expected {expected}, got {actual}")]
    SequenceMismatch {
        /// Expected sequence number.
        expected: u32,
        /// Actual sequence number received.
        actual: u32,
    },

    /// Operation not supported.
    #[error("operation not supported: {0}")]
    NotSupported(String),

    /// Parse error from util parsing functions.
    #[error("parse error: {0}")]
    Parse(#[from] ParseError),

    /// Address parsing error.
    #[error("address error: {0}")]
    Address(#[from] AddrError),

    /// Interface error (not found, invalid name).
    #[error("interface error: {0}")]
    Interface(#[from] IfError),

    /// Validation errors from configuration builders.
    #[error("validation failed: {}", format_validation_errors(.0))]
    Validation(Vec<ValidationErrorInfo>),

    /// Interface not found.
    #[error("interface not found: {name}")]
    InterfaceNotFound {
        /// The interface name that was not found.
        name: String,
    },

    /// Namespace not found.
    #[error("namespace not found: {name}")]
    NamespaceNotFound {
        /// The namespace name that was not found.
        name: String,
    },

    /// Qdisc not found.
    #[error("qdisc not found: {kind} on {interface}")]
    QdiscNotFound {
        /// The qdisc kind (e.g., "netem", "htb").
        kind: String,
        /// The interface name.
        interface: String,
    },

    /// Generic Netlink family not found.
    #[error("GENL family not found: {name}")]
    FamilyNotFound {
        /// The family name that was not found.
        name: String,
    },

    /// Operation timed out.
    ///
    /// The configured timeout expired before the kernel responded.
    /// This typically indicates a kernel bug or an extremely loaded system.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use nlink::{Connection, Route};
    /// use std::time::Duration;
    ///
    /// let conn = Connection::<Route>::new()?
    ///     .timeout(Duration::from_secs(5));
    ///
    /// match conn.get_links().await {
    ///     Err(e) if e.is_timeout() => eprintln!("kernel not responding"),
    ///     Err(e) => return Err(e),
    ///     Ok(links) => { /* ... */ }
    /// }
    /// ```
    #[error("operation timed out")]
    Timeout,
}

/// Structured validation error information.
#[derive(Debug, Clone)]
pub struct ValidationErrorInfo {
    /// Field that failed validation.
    pub field: String,
    /// Description of the error.
    pub message: String,
}

impl ValidationErrorInfo {
    /// Create a new validation error.
    pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            field: field.into(),
            message: message.into(),
        }
    }
}

impl std::fmt::Display for ValidationErrorInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {}", self.field, self.message)
    }
}

/// Format validation errors for display.
fn format_validation_errors(errors: &[ValidationErrorInfo]) -> String {
    errors
        .iter()
        .map(|e| e.to_string())
        .collect::<Vec<_>>()
        .join("; ")
}

impl Error {
    /// Create a kernel error from an errno value.
    pub fn from_errno(errno: i32) -> Self {
        let message = io::Error::from_raw_os_error(-errno).to_string();
        Self::Kernel {
            errno: -errno,
            message,
        }
    }

    /// Create a kernel error with operation context.
    pub fn from_errno_with_context(errno: i32, operation: impl Into<String>) -> Self {
        let message = io::Error::from_raw_os_error(-errno).to_string();
        Self::KernelWithContext {
            operation: operation.into(),
            errno: -errno,
            message,
        }
    }

    /// Add context to this error.
    ///
    /// Wraps kernel errors with operation context. Other errors are returned unchanged.
    pub fn with_context(self, operation: impl Into<String>) -> Self {
        match self {
            Self::Kernel { errno, message } => Self::KernelWithContext {
                operation: operation.into(),
                errno,
                message,
            },
            other => other,
        }
    }

    /// Create a validation error from a list of field errors.
    pub fn validation(errors: impl IntoIterator<Item = ValidationErrorInfo>) -> Self {
        Self::Validation(errors.into_iter().collect())
    }

    /// Create a single validation error.
    pub fn validation_error(field: impl Into<String>, message: impl Into<String>) -> Self {
        Self::Validation(vec![ValidationErrorInfo::new(field, message)])
    }

    /// Create an invalid message error.
    ///
    /// This is a convenience constructor for the common case of creating
    /// an `InvalidMessage` error with a formatted message.
    ///
    /// # Example
    ///
    /// ```ignore
    /// use nlink::netlink::Error;
    ///
    /// let err = Error::invalid_message("invalid MAC address format");
    /// ```
    pub fn invalid_message(message: impl Into<String>) -> Self {
        Self::InvalidMessage(message.into())
    }

    /// Create an invalid attribute error.
    pub fn invalid_attribute(message: impl Into<String>) -> Self {
        Self::InvalidAttribute(message.into())
    }

    /// Create a not supported error.
    pub fn not_supported(message: impl Into<String>) -> Self {
        Self::NotSupported(message.into())
    }

    /// Create an interface not found error.
    pub fn interface_not_found(name: impl Into<String>) -> Self {
        Self::InterfaceNotFound { name: name.into() }
    }

    /// Create a namespace not found error.
    pub fn namespace_not_found(name: impl Into<String>) -> Self {
        Self::NamespaceNotFound { name: name.into() }
    }

    /// Create a qdisc not found error.
    pub fn qdisc_not_found(kind: impl Into<String>, interface: impl Into<String>) -> Self {
        Self::QdiscNotFound {
            kind: kind.into(),
            interface: interface.into(),
        }
    }

    /// Create a GENL family not found error.
    pub fn family_not_found(name: impl Into<String>) -> Self {
        Self::FamilyNotFound { name: name.into() }
    }

    /// Check if this is a "not found" error (ENOENT, ENODEV, etc.).
    pub fn is_not_found(&self) -> bool {
        match self {
            Self::Kernel { errno, .. } | Self::KernelWithContext { errno, .. } => {
                matches!(*errno, 2 | 19) // ENOENT=2, ENODEV=19
            }
            Self::Interface(IfError::NotFound(_)) => true,
            Self::InterfaceNotFound { .. }
            | Self::NamespaceNotFound { .. }
            | Self::QdiscNotFound { .. }
            | Self::FamilyNotFound { .. } => true,
            _ => false,
        }
    }

    /// Check if this is a permission error (EPERM, EACCES).
    pub fn is_permission_denied(&self) -> bool {
        match self {
            Self::Kernel { errno, .. } | Self::KernelWithContext { errno, .. } => {
                matches!(*errno, 1 | 13) // EPERM=1, EACCES=13
            }
            _ => false,
        }
    }

    /// Check if this is a "already exists" error (EEXIST).
    pub fn is_already_exists(&self) -> bool {
        match self {
            Self::Kernel { errno, .. } | Self::KernelWithContext { errno, .. } => {
                *errno == 17 // EEXIST=17
            }
            _ => false,
        }
    }

    /// Check if this is a "device busy" error (EBUSY).
    pub fn is_busy(&self) -> bool {
        match self {
            Self::Kernel { errno, .. } | Self::KernelWithContext { errno, .. } => {
                *errno == 16 // EBUSY=16
            }
            _ => false,
        }
    }

    /// Get the errno value if this is a kernel error.
    pub fn errno(&self) -> Option<i32> {
        match self {
            Self::Kernel { errno, .. } | Self::KernelWithContext { errno, .. } => Some(*errno),
            _ => None,
        }
    }

    /// Check if this is an "invalid argument" error (EINVAL).
    ///
    /// This typically indicates that the kernel rejected the request
    /// due to invalid parameters (e.g., invalid handle, unsupported option).
    pub fn is_invalid_argument(&self) -> bool {
        self.errno() == Some(libc::EINVAL)
    }

    /// Check if this is a "no such device" error (ENODEV).
    ///
    /// This indicates the specified network device does not exist.
    pub fn is_no_device(&self) -> bool {
        self.errno() == Some(libc::ENODEV)
    }

    /// Check if this is a "not supported" error (EOPNOTSUPP).
    ///
    /// This indicates the requested operation is not supported by the kernel
    /// or the specific device/driver.
    pub fn is_not_supported(&self) -> bool {
        self.errno() == Some(libc::EOPNOTSUPP)
    }

    /// Check if this is a "network unreachable" error (ENETUNREACH).
    pub fn is_network_unreachable(&self) -> bool {
        self.errno() == Some(libc::ENETUNREACH)
    }

    /// Check if this is a timeout error.
    ///
    /// Returns `true` for both the [`Error::Timeout`] variant (from
    /// [`Connection::timeout`]) and kernel ETIMEDOUT errors.
    pub fn is_timeout(&self) -> bool {
        matches!(self, Self::Timeout) || self.errno() == Some(libc::ETIMEDOUT)
    }

    /// Check if this is an "address already in use" error (EADDRINUSE).
    ///
    /// This typically occurs when trying to add an IP address that is
    /// already assigned to an interface.
    pub fn is_address_in_use(&self) -> bool {
        self.errno() == Some(libc::EADDRINUSE)
    }

    /// Check if this is a "name too long" error (ENAMETOOLONG).
    ///
    /// Interface names in Linux are limited to 15 characters (IFNAMSIZ - 1).
    pub fn is_name_too_long(&self) -> bool {
        self.errno() == Some(libc::ENAMETOOLONG)
    }

    /// Check if this is a "resource temporarily unavailable" error (EAGAIN).
    ///
    /// This may occur during transient resource contention.
    pub fn is_try_again(&self) -> bool {
        self.errno() == Some(libc::EAGAIN)
    }

    /// Check if this is a "no buffer space available" error (ENOBUFS).
    ///
    /// This typically occurs when the kernel cannot allocate memory
    /// for network operations.
    pub fn is_no_buffer_space(&self) -> bool {
        self.errno() == Some(libc::ENOBUFS)
    }

    /// Check if this is a "connection refused" error (ECONNREFUSED).
    pub fn is_connection_refused(&self) -> bool {
        self.errno() == Some(libc::ECONNREFUSED)
    }

    /// Check if this is a "host unreachable" error (EHOSTUNREACH).
    pub fn is_host_unreachable(&self) -> bool {
        self.errno() == Some(libc::EHOSTUNREACH)
    }

    /// Check if this is a "message too long" error (EMSGSIZE).
    ///
    /// This occurs when a netlink message exceeds the maximum size.
    pub fn is_message_too_long(&self) -> bool {
        self.errno() == Some(libc::EMSGSIZE)
    }

    /// Check if this is a "too many open files" error (EMFILE).
    pub fn is_too_many_open_files(&self) -> bool {
        self.errno() == Some(libc::EMFILE)
    }

    /// Check if this is a "read-only file system" error (EROFS).
    ///
    /// This can occur when trying to modify network configuration
    /// in a read-only namespace or container.
    pub fn is_read_only(&self) -> bool {
        self.errno() == Some(libc::EROFS)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_errno() {
        let err = Error::from_errno(-1); // EPERM
        assert!(err.is_permission_denied());
        assert_eq!(err.errno(), Some(1));
    }

    #[test]
    fn test_from_errno_with_context() {
        let err = Error::from_errno_with_context(-2, "deleting interface eth0"); // ENOENT
        assert!(err.is_not_found());
        let msg = err.to_string();
        assert!(msg.contains("deleting interface eth0"));
        assert!(msg.contains("No such file or directory"));
    }

    #[test]
    fn test_with_context() {
        let err = Error::from_errno(-13); // EACCES
        let err = err.with_context("setting link up on eth0");
        assert!(err.is_permission_denied());
        let msg = err.to_string();
        assert!(msg.contains("setting link up on eth0"));
    }

    #[test]
    fn test_is_not_found() {
        assert!(Error::from_errno(-2).is_not_found()); // ENOENT
        assert!(Error::from_errno(-19).is_not_found()); // ENODEV
        assert!(
            Error::InterfaceNotFound {
                name: "eth0".into()
            }
            .is_not_found()
        );
        assert!(
            Error::NamespaceNotFound {
                name: "test".into()
            }
            .is_not_found()
        );
        assert!(
            Error::QdiscNotFound {
                kind: "netem".into(),
                interface: "eth0".into()
            }
            .is_not_found()
        );
    }

    #[test]
    fn test_is_busy() {
        assert!(Error::from_errno(-16).is_busy()); // EBUSY
        assert!(!Error::from_errno(-1).is_busy()); // EPERM is not busy
    }

    #[test]
    fn test_timeout() {
        let err = Error::Timeout;
        assert!(err.is_timeout());
        assert_eq!(err.to_string(), "operation timed out");

        // Kernel ETIMEDOUT should also match
        let err = Error::from_errno(-(libc::ETIMEDOUT));
        assert!(err.is_timeout());
    }

    #[test]
    fn test_error_messages() {
        let err = Error::InterfaceNotFound {
            name: "eth0".into(),
        };
        assert_eq!(err.to_string(), "interface not found: eth0");

        let err = Error::NamespaceNotFound {
            name: "myns".into(),
        };
        assert_eq!(err.to_string(), "namespace not found: myns");

        let err = Error::QdiscNotFound {
            kind: "netem".into(),
            interface: "docker0".into(),
        };
        assert_eq!(err.to_string(), "qdisc not found: netem on docker0");
    }
}
