//! Error types for ACP connector
//!
//! This module defines error types specific to ACP connector operations,
//! including transport failures, protocol errors, and configuration issues.

use thiserror::Error;

/// Result type for ACP connector operations
pub type AcpResult<T> = Result<T, AcpError>;

/// Errors that can occur during ACP connector operations
///
/// This error type covers all failure modes of the ACP connector, from
/// low-level transport issues to high-level protocol violations.
#[derive(Debug, Error)]
pub enum AcpError {
    /// Transport layer error (connection failure, I/O error, etc.)
    ///
    /// Wraps errors from the transport layer (stdio or HTTP) that prevent
    /// communication with the agent.
    ///
    /// # Examples
    ///
    /// - Process failed to spawn
    /// - Network connection refused
    /// - Broken pipe (process terminated)
    /// - SSL/TLS handshake failed
    #[error("Transport error: {0}")]
    Transport(String),

    /// Protocol layer error (JSON-RPC format, invalid response, etc.)
    ///
    /// Indicates a violation of the ACP/JSON-RPC protocol, such as
    /// malformed messages or unexpected response structure.
    ///
    /// # Examples
    ///
    /// - Invalid JSON syntax
    /// - Missing required JSON-RPC fields
    /// - Incorrect message format
    /// - Protocol version mismatch
    #[error("Protocol error: {0}")]
    Protocol(String),

    /// Request timeout (no response received within timeout period)
    ///
    /// The agent did not respond to a request within the configured timeout.
    /// This may indicate agent unresponsiveness, network issues, or very
    /// slow operations.
    #[error("Request timeout after {timeout_ms}ms")]
    Timeout {
        /// Timeout duration in milliseconds
        timeout_ms: u64,
    },

    /// Configuration error (invalid settings, validation failure, etc.)
    ///
    /// The connector configuration is invalid or incomplete, preventing
    /// connector creation or connection attempts.
    ///
    /// # Examples
    ///
    /// - Empty command for stdio transport
    /// - Invalid URL for HTTP transport
    /// - Zero timeout value
    /// - Invalid protocol version
    #[error("Configuration error: {0}")]
    Config(String),

    /// Initialization error (agent initialization failed)
    ///
    /// The agent rejected the initialization request or returned an error
    /// during the handshake phase.
    ///
    /// # Examples
    ///
    /// - Unsupported protocol version
    /// - Agent capabilities mismatch
    /// - Authentication failure
    #[error("Initialization failed: {0}")]
    Initialization(String),

    /// Session operation error (session/new, session/prompt, etc.)
    ///
    /// An error occurred during a session-related operation like creating
    /// a new session or sending a prompt.
    ///
    /// # Examples
    ///
    /// - Session ID not found
    /// - Session already terminated
    /// - Invalid prompt format
    /// - Resource exhaustion
    #[error("Session operation failed: {0}")]
    SessionOperation(String),

    /// Internal error (unexpected state, logic error, etc.)
    ///
    /// An internal error in the connector implementation. This typically
    /// indicates a bug and should be reported.
    ///
    /// # Examples
    ///
    /// - Channel unexpectedly closed
    /// - Lock poisoned
    /// - Invalid state transition
    #[error("Internal error: {0}")]
    Internal(String),

    /// Agent returned a JSON-RPC error response
    ///
    /// The agent responded with a JSON-RPC error object instead of a
    /// successful result.
    #[error("Agent error: {message} (code: {code})")]
    AgentError {
        /// JSON-RPC error code
        code: i64,
        /// Error message from agent
        message: String,
        /// Optional additional error data
        data: Option<serde_json::Value>,
    },
}

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

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

    /// Create a timeout error
    pub fn timeout(timeout_ms: u64) -> Self {
        Self::Timeout { timeout_ms }
    }

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

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

    /// Create a session operation error
    pub fn session_operation(msg: impl Into<String>) -> Self {
        Self::SessionOperation(msg.into())
    }

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

    /// Create an agent error from JSON-RPC error object
    pub fn agent_error(code: i64, message: impl Into<String>, data: Option<serde_json::Value>) -> Self {
        Self::AgentError {
            code,
            message: message.into(),
            data,
        }
    }

    /// Check if this error is retriable (should attempt reconnection)
    ///
    /// Returns true for transient errors like transport failures and timeouts.
    /// Returns false for permanent errors like configuration issues.
    pub fn is_retriable(&self) -> bool {
        matches!(
            self,
            AcpError::Transport(_) | AcpError::Timeout { .. } | AcpError::Internal(_)
        )
    }
}

// Conversion from protocol handler errors
impl From<crate::connectors::acp::protocol::ProtocolError> for AcpError {
    fn from(err: crate::connectors::acp::protocol::ProtocolError) -> Self {
        use crate::connectors::acp::protocol::ProtocolError;

        match err {
            ProtocolError::Timeout { timeout_ms } => AcpError::Timeout { timeout_ms },
            ProtocolError::JsonRpcError { code, message, data } => {
                AcpError::AgentError { code, message, data }
            }
            ProtocolError::InvalidMessage(msg) => AcpError::Protocol(msg),
            ProtocolError::Internal(msg) => AcpError::Internal(msg),
        }
    }
}

// Conversion from transport errors (boxed error)
impl From<Box<dyn std::error::Error + Send + Sync>> for AcpError {
    fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
        AcpError::Transport(err.to_string())
    }
}

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

    #[test]
    fn test_transport_error() {
        let err = AcpError::transport("Connection refused");
        assert_eq!(err.to_string(), "Transport error: Connection refused");
    }

    #[test]
    fn test_protocol_error() {
        let err = AcpError::protocol("Invalid JSON");
        assert_eq!(err.to_string(), "Protocol error: Invalid JSON");
    }

    #[test]
    fn test_timeout_error() {
        let err = AcpError::timeout(30000);
        assert_eq!(err.to_string(), "Request timeout after 30000ms");
    }

    #[test]
    fn test_config_error() {
        let err = AcpError::config("Empty command");
        assert_eq!(err.to_string(), "Configuration error: Empty command");
    }

    #[test]
    fn test_initialization_error() {
        let err = AcpError::initialization("Protocol version mismatch");
        assert_eq!(err.to_string(), "Initialization failed: Protocol version mismatch");
    }

    #[test]
    fn test_session_operation_error() {
        let err = AcpError::session_operation("Session not found");
        assert_eq!(err.to_string(), "Session operation failed: Session not found");
    }

    #[test]
    fn test_internal_error() {
        let err = AcpError::internal("Channel closed");
        assert_eq!(err.to_string(), "Internal error: Channel closed");
    }

    #[test]
    fn test_agent_error() {
        let err = AcpError::agent_error(-32601, "Method not found", None);
        assert_eq!(err.to_string(), "Agent error: Method not found (code: -32601)");
    }

    #[test]
    fn test_is_retriable() {
        assert!(AcpError::transport("test").is_retriable());
        assert!(AcpError::timeout(1000).is_retriable());
        assert!(AcpError::internal("test").is_retriable());

        assert!(!AcpError::config("test").is_retriable());
        assert!(!AcpError::protocol("test").is_retriable());
        assert!(!AcpError::initialization("test").is_retriable());
    }

    #[test]
    fn test_from_protocol_error_timeout() {
        use crate::connectors::acp::protocol::ProtocolError;

        let protocol_err = ProtocolError::Timeout { timeout_ms: 5000 };
        let acp_err: AcpError = protocol_err.into();

        match acp_err {
            AcpError::Timeout { timeout_ms } => assert_eq!(timeout_ms, 5000),
            _ => panic!("Expected Timeout variant"),
        }
    }

    #[test]
    fn test_from_protocol_error_jsonrpc() {
        use crate::connectors::acp::protocol::ProtocolError;

        let protocol_err = ProtocolError::JsonRpcError {
            code: -32600,
            message: "Invalid Request".to_string(),
            data: None,
        };
        let acp_err: AcpError = protocol_err.into();

        match acp_err {
            AcpError::AgentError { code, message, .. } => {
                assert_eq!(code, -32600);
                assert_eq!(message, "Invalid Request");
            }
            _ => panic!("Expected AgentError variant"),
        }
    }
}
