//! Error types for kb-core

use std::io;
use std::path::PathBuf;

use thiserror::Error;

/// Result type alias using `KbError`
pub type Result<T> = std::result::Result<T, KbError>;

/// Main error type for kb-core operations
#[derive(Debug, Error)]
pub enum KbError {
    /// I/O errors (file operations, etc.)
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),

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

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

    /// Nickel export errors (when loading .ncl files)
    #[error("Nickel export failed: {0}")]
    NickelExport(String),

    /// Storage backend errors
    #[error("Storage error: {0}")]
    Storage(String),

    /// Node not found
    #[error("Node not found: {0}")]
    NodeNotFound(String),

    /// Edge operation error
    #[error("Edge error: {0}")]
    Edge(String),

    /// Graph operation error
    #[error("Graph error: {0}")]
    Graph(String),

    /// Markdown parsing error
    #[error("Markdown parsing error: {0}")]
    Parser(String),

    /// YAML frontmatter error
    #[error("Frontmatter error: {0}")]
    Frontmatter(String),

    /// Embedding generation error
    #[error("Embedding error: {0}")]
    Embedding(String),

    /// Query execution error
    #[error("Query error: {0}")]
    Query(String),

    /// Template rendering error
    #[error("Template error: {0}")]
    Template(String),

    /// Sync operation error
    #[error("Sync error: {0}")]
    Sync(String),

    /// Inheritance resolution error
    #[error("Inheritance error: {0}")]
    Inheritance(String),

    /// Invalid file path
    #[error("Invalid path: {}", .0.display())]
    InvalidPath(PathBuf),

    /// Missing required field
    #[error("Missing required field: {0}")]
    MissingField(String),

    /// Invalid node type
    #[error("Invalid node type: {0}")]
    InvalidNodeType(String),

    /// Invalid edge type
    #[error("Invalid edge type: {0}")]
    InvalidEdgeType(String),

    /// Other errors
    #[error("{0}")]
    Other(String),
}

impl From<serde_json::Error> for KbError {
    fn from(err: serde_json::Error) -> Self {
        Self::Serialization(err.to_string())
    }
}

impl From<serde_yaml::Error> for KbError {
    fn from(err: serde_yaml::Error) -> Self {
        Self::Serialization(err.to_string())
    }
}

impl From<toml::de::Error> for KbError {
    fn from(err: toml::de::Error) -> Self {
        Self::Serialization(err.to_string())
    }
}

impl From<toml::ser::Error> for KbError {
    fn from(err: toml::ser::Error) -> Self {
        Self::Serialization(err.to_string())
    }
}

impl From<tera::Error> for KbError {
    fn from(err: tera::Error) -> Self {
        Self::Template(err.to_string())
    }
}

#[cfg(test)]
mod tests {
    use std::io;

    use super::*;

    #[test]
    fn test_io_error_conversion() {
        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
        let kb_err: KbError = io_err.into();
        assert!(matches!(kb_err, KbError::Io(_)));
    }

    #[test]
    fn test_serialization_error_display() {
        let err = KbError::Serialization("test error".to_string());
        assert_eq!(err.to_string(), "Serialization error: test error");
    }

    #[test]
    fn test_node_not_found_error() {
        let err = KbError::NodeNotFound("node-123".to_string());
        assert_eq!(err.to_string(), "Node not found: node-123");
    }

    #[test]
    fn test_config_error() {
        let err = KbError::Config("missing required field".to_string());
        assert_eq!(
            err.to_string(),
            "Configuration error: missing required field"
        );
    }

    #[test]
    fn test_invalid_path_error() {
        let path = PathBuf::from("/invalid/path");
        let err = KbError::InvalidPath(path.clone());
        assert!(err.to_string().contains("Invalid path"));
        assert!(err.to_string().contains("/invalid/path"));
    }

    #[test]
    fn test_json_error_conversion() {
        let json_str = "{invalid json}";
        let json_err = serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
        let kb_err: KbError = json_err.into();
        assert!(matches!(kb_err, KbError::Serialization(_)));
    }

    #[test]
    fn test_missing_field_error() {
        let err = KbError::MissingField("title".to_string());
        assert_eq!(err.to_string(), "Missing required field: title");
    }
}
