//! Error types for spn-native.

use spn_core::BackendError;
use std::path::PathBuf;
use thiserror::Error;

/// Result type alias for spn-native operations.
pub type Result<T> = std::result::Result<T, NativeError>;

/// Errors that can occur in spn-native operations.
#[derive(Error, Debug)]
pub enum NativeError {
    /// HTTP request failed.
    #[error("HTTP request failed: {0}")]
    Http(#[from] reqwest::Error),

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

    /// Model not found on HuggingFace.
    #[error("Model not found: {repo}/{filename}")]
    ModelNotFound {
        /// HuggingFace repository.
        repo: String,
        /// Requested filename.
        filename: String,
    },

    /// Checksum verification failed.
    #[error("Checksum mismatch for {path}: expected {expected}, got {actual}")]
    ChecksumMismatch {
        /// File path.
        path: PathBuf,
        /// Expected SHA256.
        expected: String,
        /// Actual SHA256.
        actual: String,
    },

    /// Invalid model configuration.
    #[error("Invalid model configuration: {0}")]
    InvalidConfig(String),

    /// Download was interrupted.
    #[error("Download interrupted: {0}")]
    Interrupted(String),

    /// Storage directory error.
    #[error("Storage directory error: {0}")]
    StorageDir(String),

    /// JSON parsing error.
    #[error("JSON parse error: {0}")]
    Json(#[from] serde_json::Error),

    // ========================================================================
    // Inference errors (feature = "inference")
    // These variants are reserved for future inference features like:
    // - Detailed error recovery (InferenceFailed)
    // - Architecture validation (UnsupportedArchitecture)
    // - GPU/CPU device selection (DeviceError)
    // - Custom tokenizer support (TokenizerError)
    // ========================================================================
    /// Model not loaded.
    #[error("No model loaded")]
    ModelNotLoaded,

    /// Inference failed.
    ///
    /// Reserved for detailed inference error reporting (e.g., OOM, timeout).
    #[allow(dead_code)] // Reserved for future inference error handling
    #[error("Inference failed: {0}")]
    InferenceFailed(String),

    /// Unsupported model architecture.
    ///
    /// Reserved for architecture validation when loading models.
    #[allow(dead_code)] // Reserved for architecture validation
    #[error("Unsupported architecture: {0}")]
    UnsupportedArchitecture(String),

    /// Device error (GPU/CPU).
    ///
    /// Reserved for GPU/CPU device selection and error handling.
    #[allow(dead_code)] // Reserved for device management
    #[error("Device error: {0}")]
    DeviceError(String),

    /// Tokenizer error.
    ///
    /// Reserved for custom tokenizer support.
    #[allow(dead_code)] // Reserved for custom tokenizer support
    #[error("Tokenizer error: {0}")]
    TokenizerError(String),
}

impl From<NativeError> for BackendError {
    fn from(err: NativeError) -> Self {
        match err {
            NativeError::Http(e) => BackendError::NetworkError(e.to_string()),
            NativeError::Io(e) => BackendError::StorageError(e.to_string()),
            NativeError::ModelNotFound { repo, filename } => {
                BackendError::ModelNotFound(format!("{repo}/{filename}"))
            }
            NativeError::ChecksumMismatch {
                expected, actual, ..
            } => BackendError::ChecksumError { expected, actual },
            NativeError::InvalidConfig(msg) => BackendError::InvalidConfig(msg),
            NativeError::Interrupted(msg) => BackendError::DownloadError(msg),
            NativeError::StorageDir(msg) => BackendError::StorageError(msg),
            NativeError::Json(e) => BackendError::ParseError(e.to_string()),
            // Inference errors
            NativeError::ModelNotLoaded => {
                BackendError::BackendSpecific("No model loaded".to_string())
            }
            NativeError::InferenceFailed(msg) => BackendError::BackendSpecific(msg),
            NativeError::UnsupportedArchitecture(arch) => {
                BackendError::InvalidConfig(format!("Unsupported architecture: {arch}"))
            }
            NativeError::DeviceError(msg) => BackendError::BackendSpecific(msg),
            NativeError::TokenizerError(msg) => BackendError::BackendSpecific(msg),
        }
    }
}

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

    #[test]
    fn test_error_display() {
        let err = NativeError::ModelNotFound {
            repo: "test/repo".to_string(),
            filename: "model.gguf".to_string(),
        };
        assert!(err.to_string().contains("test/repo"));
        assert!(err.to_string().contains("model.gguf"));
    }

    #[test]
    fn test_checksum_error() {
        let err = NativeError::ChecksumMismatch {
            path: PathBuf::from("/tmp/model.gguf"),
            expected: "abc123".to_string(),
            actual: "def456".to_string(),
        };
        assert!(err.to_string().contains("abc123"));
        assert!(err.to_string().contains("def456"));
    }

    #[test]
    fn test_into_backend_error() {
        let err = NativeError::InvalidConfig("bad config".to_string());
        let backend_err: BackendError = err.into();
        assert!(matches!(backend_err, BackendError::InvalidConfig(_)));
    }
}
