//! MCP error types with builder pattern
//!
//! Provides rich error types inspired by the Go mcphost implementation

use serde_json::Value;
use std::collections::HashMap;
use std::fmt;
use thiserror::Error;

use super::types::JsonRpcError;

/// Error type categories
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorType {
    /// Validation error
    Validation,
    /// Resource not found
    NotFound,
    /// Method not found
    MethodNotFound,
    /// Unauthorized access
    Unauthorized,
    /// Internal server error
    Internal,
    /// Request timeout
    Timeout,
    /// Request cancelled
    Cancelled,
    /// Rate limit exceeded
    RateLimit,
    /// Resource conflict
    Conflict,
    /// Circuit breaker open
    CircuitOpen,
}

impl fmt::Display for ErrorType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ErrorType::Validation => write!(f, "validation"),
            ErrorType::NotFound => write!(f, "not_found"),
            ErrorType::MethodNotFound => write!(f, "method_not_found"),
            ErrorType::Unauthorized => write!(f, "unauthorized"),
            ErrorType::Internal => write!(f, "internal"),
            ErrorType::Timeout => write!(f, "timeout"),
            ErrorType::Cancelled => write!(f, "cancelled"),
            ErrorType::RateLimit => write!(f, "rate_limit"),
            ErrorType::Conflict => write!(f, "conflict"),
            ErrorType::CircuitOpen => write!(f, "circuit_open"),
        }
    }
}

/// Rich MCP error with context
#[derive(Debug, Error)]
pub struct McpError {
    /// Error type
    pub error_type: ErrorType,
    /// Error code (string identifier)
    pub code: String,
    /// Error message
    pub message: String,
    /// Additional details
    pub details: HashMap<String, Value>,
    /// Underlying cause
    #[source]
    pub cause: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl fmt::Display for McpError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}] {}: {}", self.error_type, self.code, self.message)
    }
}

impl McpError {
    /// Convert to JSON-RPC error
    pub fn to_jsonrpc(&self) -> JsonRpcError {
        let code = match self.error_type {
            ErrorType::Validation => -32602,     // Invalid params
            ErrorType::NotFound => -32002,       // Resource not found
            ErrorType::MethodNotFound => -32601, // Method not found
            ErrorType::Unauthorized => -32001,   // Unauthorized
            ErrorType::Internal => -32603,       // Internal error
            ErrorType::Timeout => -32000,        // Server error
            ErrorType::Cancelled => -32000,      // Server error
            ErrorType::RateLimit => -32000,      // Server error
            ErrorType::Conflict => -32000,       // Server error
            ErrorType::CircuitOpen => -32000,    // Server error (service unavailable)
        };

        let mut data = self.details.clone();
        data.insert(
            "type".to_string(),
            Value::String(self.error_type.to_string()),
        );
        data.insert("code".to_string(), Value::String(self.code.clone()));

        JsonRpcError::new(code, &self.message).with_data(serde_json::to_value(data).unwrap())
    }

    /// Start building a new error
    pub fn builder(error_type: ErrorType, code: impl Into<String>) -> McpErrorBuilder {
        McpErrorBuilder {
            error_type,
            code: code.into(),
            message: String::new(),
            details: HashMap::new(),
            cause: None,
        }
    }

    /// Create a validation error
    pub fn validation(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self::builder(ErrorType::Validation, code)
            .message(message)
            .build()
    }

    /// Create a not found error
    pub fn not_found(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self::builder(ErrorType::NotFound, code)
            .message(message)
            .build()
    }

    /// Create an internal error
    pub fn internal(code: impl Into<String>, message: impl Into<String>) -> Self {
        Self::builder(ErrorType::Internal, code)
            .message(message)
            .build()
    }

    /// Create a method not found error
    pub fn method_not_found(method: impl Into<String>) -> Self {
        let method = method.into();
        Self::builder(ErrorType::MethodNotFound, "method_not_found")
            .message(format!("Method not found: {}", method))
            .detail("method", method)
            .build()
    }

    /// Create a not implemented error
    pub fn not_implemented(message: impl Into<String>) -> Self {
        Self::builder(ErrorType::Internal, "not_implemented")
            .message(message)
            .build()
    }

    /// Create a rate limited error
    pub fn rate_limited(message: impl Into<String>) -> Self {
        Self::builder(ErrorType::RateLimit, "rate_limit_exceeded")
            .message(message)
            .build()
    }

    /// Create a circuit breaker open error
    pub fn circuit_open(tool_name: impl Into<String>, message: impl Into<String>) -> Self {
        let tool = tool_name.into();
        Self::builder(ErrorType::CircuitOpen, "circuit_breaker_open")
            .message(message)
            .detail("tool", tool)
            .build()
    }
}

/// Builder for McpError
pub struct McpErrorBuilder {
    error_type: ErrorType,
    code: String,
    message: String,
    details: HashMap<String, Value>,
    cause: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl McpErrorBuilder {
    /// Set error message
    pub fn message(mut self, message: impl Into<String>) -> Self {
        self.message = message.into();
        self
    }

    /// Add a detail field
    pub fn detail(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
        self.details.insert(key.into(), value.into());
        self
    }

    /// Set the underlying cause
    pub fn cause(mut self, cause: impl std::error::Error + Send + Sync + 'static) -> Self {
        self.cause = Some(Box::new(cause));
        self
    }

    /// Build the error
    pub fn build(self) -> McpError {
        McpError {
            error_type: self.error_type,
            code: self.code,
            message: self.message,
            details: self.details,
            cause: self.cause,
        }
    }
}

/// MCP-specific error codes
pub mod codes {
    /// Resource not found
    pub const RESOURCE_NOT_FOUND: &str = "RESOURCE_NOT_FOUND";
    /// Tool not found
    pub const TOOL_NOT_FOUND: &str = "TOOL_NOT_FOUND";
    /// Prompt not found
    pub const PROMPT_NOT_FOUND: &str = "PROMPT_NOT_FOUND";
    /// Invalid tool arguments
    pub const INVALID_TOOL_ARGS: &str = "INVALID_TOOL_ARGS";
    /// Tool execution failed
    pub const TOOL_EXECUTION_FAILED: &str = "TOOL_EXECUTION_FAILED";
    /// Circuit breaker open
    pub const CIRCUIT_BREAKER_OPEN: &str = "CIRCUIT_BREAKER_OPEN";
    /// Rate limit exceeded
    pub const RATE_LIMIT_EXCEEDED: &str = "RATE_LIMIT_EXCEEDED";
}

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

    #[test]
    fn test_error_builder() {
        let error = McpError::builder(ErrorType::Validation, "TEST_ERROR")
            .message("Test error message")
            .detail("field", "value")
            .build();

        assert_eq!(error.error_type, ErrorType::Validation);
        assert_eq!(error.code, "TEST_ERROR");
        assert_eq!(error.message, "Test error message");
        assert_eq!(
            error.details.get("field").unwrap(),
            &Value::String("value".to_string())
        );
    }

    #[test]
    fn test_to_jsonrpc() {
        let error = McpError::validation("INVALID_PARAM", "Parameter X is invalid");
        let json_err = error.to_jsonrpc();

        assert_eq!(json_err.code, -32602);
        assert_eq!(json_err.message, "Parameter X is invalid");
    }

    #[test]
    fn test_convenience_constructors() {
        let err = McpError::not_found("TEST", "not found");
        assert_eq!(err.error_type, ErrorType::NotFound);

        let err = McpError::internal("TEST", "internal");
        assert_eq!(err.error_type, ErrorType::Internal);
    }
}
