//! Error types for Nightlight
//!
//! This module defines the error types used throughout the Nightlight system.
//! We use thiserror for convenient error definition and anyhow for error propagation.

use thiserror::Error;

/// Main error type for Nightlight operations
#[derive(Error, Debug)]
pub enum Error {
    /// Storage-related errors (disk I/O, corruption, etc.)
    #[error("Storage error: {0}")]
    Storage(String),

    /// Query execution errors (invalid PromQL, timeout, etc.)
    #[error("Query error: {0}")]
    Query(String),

    /// Invalid metric or label data
    #[error("Invalid metric: {0}")]
    InvalidMetric(String),

    /// Invalid label name or value
    #[error("Invalid label: {0}")]
    InvalidLabel(String),

    /// Invalid sample data (NaN, Inf, out of order, etc.)
    #[error("Invalid sample: {0}")]
    InvalidSample(String),

    /// Time range errors (invalid start/end, etc.)
    #[error("Invalid time range: {0}")]
    InvalidTimeRange(String),

    /// Series not found in storage
    #[error("Series not found: {0}")]
    SeriesNotFound(String),

    /// I/O errors
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

    /// Serialization/deserialization errors
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// Configuration errors
    #[error("Configuration error: {0}")]
    Config(String),

    /// Network/gRPC errors
    #[error("Network error: {0}")]
    Network(String),

    /// Resource exhaustion (out of memory, disk full, etc.)
    #[error("Resource exhausted: {0}")]
    ResourceExhausted(String),

    /// Operation timeout
    #[error("Operation timed out: {0}")]
    Timeout(String),

    /// Internal server errors (bugs, panics, etc.)
    #[error("Internal error: {0}")]
    Internal(String),
}

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

impl Error {
    /// Create a storage error
    pub fn storage(msg: impl Into<String>) -> Self {
        Self::Storage(msg.into())
    }

    /// Create a query error
    pub fn query(msg: impl Into<String>) -> Self {
        Self::Query(msg.into())
    }

    /// Create an invalid metric error
    pub fn invalid_metric(msg: impl Into<String>) -> Self {
        Self::InvalidMetric(msg.into())
    }

    /// Create an invalid label error
    pub fn invalid_label(msg: impl Into<String>) -> Self {
        Self::InvalidLabel(msg.into())
    }

    /// Create an invalid sample error
    pub fn invalid_sample(msg: impl Into<String>) -> Self {
        Self::InvalidSample(msg.into())
    }

    /// Create a series not found error
    pub fn series_not_found(msg: impl Into<String>) -> Self {
        Self::SeriesNotFound(msg.into())
    }

    /// Create an internal error
    pub fn internal(msg: impl Into<String>) -> Self {
        Self::Internal(msg.into())
    }
}

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

    #[test]
    fn test_error_creation() {
        let err = Error::storage("disk failure");
        assert_eq!(err.to_string(), "Storage error: disk failure");

        let err = Error::query("invalid PromQL");
        assert_eq!(err.to_string(), "Query error: invalid PromQL");

        let err = Error::invalid_metric("missing __name__ label");
        assert_eq!(
            err.to_string(),
            "Invalid metric: missing __name__ label"
        );
    }

    #[test]
    fn test_result_type() {
        fn example_fn() -> Result<i32> {
            Ok(42)
        }

        assert_eq!(example_fn().unwrap(), 42);

        fn failing_fn() -> Result<i32> {
            Err(Error::query("test error"))
        }

        assert!(failing_fn().is_err());
    }
}
