//! Error classification and handling for ACP.
//!
//! This module implements:
//! - Error classification (transient vs terminal vs user)
//! - Retry logic with exponential backoff
//! - Error surfacing to UI
//! - JSON-RPC error code mapping

use crate::acp::transport::{JsonRpcError, TransportError};
use std::time::Duration;

/// Classification of an error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorClass {
    /// Transient error that may succeed on retry (network issues, timeouts).
    Transient,
    /// Terminal error that requires user intervention (auth failure, unsupported feature).
    Terminal,
    /// User error (invalid input, permission denied).
    User,
}

/// Classified error with context.
#[derive(Debug, Clone)]
pub struct ClassifiedError {
    /// Error classification.
    pub class: ErrorClass,
    /// JSON-RPC error code (if applicable).
    pub code: Option<i32>,
    /// User-facing message.
    pub message: String,
    /// Technical details for logs.
    pub details: Option<String>,
    /// Suggested retry delay (for transient errors).
    pub retry_after: Option<Duration>,
}

impl ClassifiedError {
    /// Create a new classified error.
    pub fn new(
        class: ErrorClass,
        message: impl Into<String>,
    ) -> Self {
        Self {
            class,
            code: None,
            message: message.into(),
            details: None,
            retry_after: None,
        }
    }

    /// Set the error code.
    pub fn with_code(mut self, code: i32) -> Self {
        self.code = Some(code);
        self
    }

    /// Set technical details.
    pub fn with_details(mut self, details: impl Into<String>) -> Self {
        self.details = Some(details.into());
        self
    }

    /// Set retry delay.
    pub fn with_retry_after(mut self, duration: Duration) -> Self {
        self.retry_after = Some(duration);
        self
    }

    /// Check if this error is retryable.
    pub fn is_retryable(&self) -> bool {
        self.class == ErrorClass::Transient
    }
}

/// Classify a JSON-RPC error.
pub fn classify_jsonrpc_error(error: &JsonRpcError) -> ClassifiedError {
    match error.code {
        // JSON-RPC standard errors
        -32700 => ClassifiedError::new(
            ErrorClass::Terminal,
            "Invalid JSON received",
        )
        .with_code(-32700)
        .with_details(error.message.clone()),

        -32600 => ClassifiedError::new(
            ErrorClass::Terminal,
            "Invalid request format",
        )
        .with_code(-32600)
        .with_details(error.message.clone()),

        -32601 => ClassifiedError::new(
            ErrorClass::Terminal,
            "Method not supported",
        )
        .with_code(-32601)
        .with_details(error.message.clone()),

        -32602 => ClassifiedError::new(
            ErrorClass::User,
            "Invalid parameters",
        )
        .with_code(-32602)
        .with_details(error.message.clone()),

        -32603 => ClassifiedError::new(
            ErrorClass::Transient,
            "Internal error, please try again",
        )
        .with_code(-32603)
        .with_details(error.message.clone()),

        // Custom error codes (examples)
        -40001 => ClassifiedError::new(
            ErrorClass::Terminal,
            "Authentication required",
        )
        .with_code(-40001)
        .with_details(error.message.clone()),

        -40003 => {
            // Rate limit error
            let retry_after = parse_retry_after(&error.data);
            ClassifiedError::new(
                ErrorClass::Transient,
                "Rate limit exceeded, please wait",
            )
            .with_code(-40003)
            .with_details(error.message.clone())
            .with_retry_after(retry_after)
        }

        -40401 => ClassifiedError::new(
            ErrorClass::User,
            error.message.clone(),
        )
        .with_code(-40401),

        // Default: treat unknown errors as transient
        _ => ClassifiedError::new(
            ErrorClass::Transient,
            error.message.clone(),
        )
        .with_code(error.code)
        .with_details(format!("Unknown error code: {}", error.code)),
    }
}

/// Classify a transport error.
pub fn classify_transport_error(error: &TransportError) -> ClassifiedError {
    match error {
        TransportError::ConnectionError(msg) => {
            ClassifiedError::new(ErrorClass::Transient, format!("Connection error: {}", msg))
                .with_retry_after(Duration::from_secs(2))
        }
        TransportError::IoError(io_err) => {
            ClassifiedError::new(ErrorClass::Transient, format!("I/O error: {}", io_err))
                .with_retry_after(Duration::from_secs(2))
        }
        TransportError::SerializationError(serde_err) => {
            ClassifiedError::new(ErrorClass::Terminal, format!("Serialization error: {}", serde_err))
        }
        TransportError::JsonRpcError(jsonrpc_err) => classify_jsonrpc_error(jsonrpc_err),
        TransportError::Closed => {
            ClassifiedError::new(ErrorClass::Terminal, "Transport closed")
        }
        TransportError::Timeout => {
            ClassifiedError::new(ErrorClass::Transient, "Request timed out")
                .with_retry_after(Duration::from_secs(5))
        }
        TransportError::ProcessExited => {
            ClassifiedError::new(ErrorClass::Terminal, "Process exited unexpectedly")
        }
        TransportError::HttpError(msg) => {
            ClassifiedError::new(ErrorClass::Transient, format!("HTTP error: {}", msg))
                .with_retry_after(Duration::from_secs(2))
        }
        TransportError::SseError(msg) => {
            ClassifiedError::new(ErrorClass::Transient, format!("SSE error: {}", msg))
                .with_retry_after(Duration::from_secs(2))
        }
        TransportError::Other(msg) => {
            ClassifiedError::new(ErrorClass::Transient, msg.clone())
        }
    }
}

/// Parse retry-after duration from error data.
///
/// This looks for a "retry_after" field in the error data that specifies
/// how long to wait before retrying.
fn parse_retry_after(data: &Option<serde_json::Value>) -> Duration {
    if let Some(serde_json::Value::Object(map)) = data {
        if let Some(serde_json::Value::Number(secs)) = map.get("retry_after") {
            if let Some(secs) = secs.as_u64() {
                return Duration::from_secs(secs);
            }
        }
    }

    // Default retry delay
    Duration::from_secs(10)
}

/// Calculate exponential backoff delay.
///
/// # Arguments
///
/// * `attempt` - The attempt number (0-indexed)
/// * `base_delay` - Base delay for first retry
/// * `max_delay` - Maximum delay cap
///
/// # Returns
///
/// The delay to wait before the next attempt.
pub fn exponential_backoff(attempt: u32, base_delay: Duration, max_delay: Duration) -> Duration {
    let delay_secs = base_delay.as_secs() * 2u64.pow(attempt);
    let delay = Duration::from_secs(delay_secs);
    delay.min(max_delay)
}

/// Severity level for UI error surfacing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
    /// Informational message.
    Info,
    /// Warning that user should be aware of.
    Warning,
    /// Error requiring user attention.
    Error,
    /// Fatal error requiring restart or reconnect.
    Fatal,
}

/// Suggested actions for errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorAction {
    /// Retry the operation.
    Retry,
    /// Reconnect to the agent.
    Reconnect,
    /// Check configuration.
    CheckConfig,
    /// Contact support.
    ContactSupport,
    /// Dismiss the error.
    Dismiss,
}

/// Map error classification to UI severity.
pub fn error_severity(classified: &ClassifiedError) -> ErrorSeverity {
    match classified.class {
        ErrorClass::Transient => ErrorSeverity::Warning,
        ErrorClass::Terminal => ErrorSeverity::Error,
        ErrorClass::User => ErrorSeverity::Info,
    }
}

/// Suggest actions for an error.
pub fn error_actions(classified: &ClassifiedError) -> Vec<ErrorAction> {
    match classified.class {
        ErrorClass::Transient => vec![ErrorAction::Retry, ErrorAction::Dismiss],
        ErrorClass::Terminal => {
            if classified.code == Some(-40001) {
                // Auth error
                vec![ErrorAction::CheckConfig, ErrorAction::ContactSupport]
            } else {
                vec![ErrorAction::CheckConfig, ErrorAction::Dismiss]
            }
        }
        ErrorClass::User => vec![ErrorAction::Dismiss],
    }
}

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

    #[test]
    fn test_classify_parse_error() {
        let error = JsonRpcError {
            code: -32700,
            message: "Parse error".to_string(),
            data: None,
        };

        let classified = classify_jsonrpc_error(&error);
        assert_eq!(classified.class, ErrorClass::Terminal);
        assert_eq!(classified.code, Some(-32700));
        assert!(!classified.is_retryable());
    }

    #[test]
    fn test_classify_invalid_params() {
        let error = JsonRpcError {
            code: -32602,
            message: "Invalid params".to_string(),
            data: None,
        };

        let classified = classify_jsonrpc_error(&error);
        assert_eq!(classified.class, ErrorClass::User);
        assert!(!classified.is_retryable());
    }

    #[test]
    fn test_classify_internal_error() {
        let error = JsonRpcError {
            code: -32603,
            message: "Internal error".to_string(),
            data: None,
        };

        let classified = classify_jsonrpc_error(&error);
        assert_eq!(classified.class, ErrorClass::Transient);
        assert!(classified.is_retryable());
    }

    #[test]
    fn test_classify_rate_limit() {
        let error = JsonRpcError {
            code: -40003,
            message: "Rate limited".to_string(),
            data: Some(serde_json::json!({"retry_after": 30})),
        };

        let classified = classify_jsonrpc_error(&error);
        assert_eq!(classified.class, ErrorClass::Transient);
        assert_eq!(classified.retry_after, Some(Duration::from_secs(30)));
    }

    #[test]
    fn test_classify_transport_timeout() {
        let error = TransportError::Timeout;
        let classified = classify_transport_error(&error);
        assert_eq!(classified.class, ErrorClass::Transient);
        assert!(classified.is_retryable());
    }

    #[test]
    fn test_classify_transport_closed() {
        let error = TransportError::Closed;
        let classified = classify_transport_error(&error);
        assert_eq!(classified.class, ErrorClass::Terminal);
        assert!(!classified.is_retryable());
    }

    #[test]
    fn test_exponential_backoff() {
        let base = Duration::from_secs(1);
        let max = Duration::from_secs(60);

        assert_eq!(exponential_backoff(0, base, max), Duration::from_secs(1));
        assert_eq!(exponential_backoff(1, base, max), Duration::from_secs(2));
        assert_eq!(exponential_backoff(2, base, max), Duration::from_secs(4));
        assert_eq!(exponential_backoff(3, base, max), Duration::from_secs(8));
        assert_eq!(exponential_backoff(10, base, max), Duration::from_secs(60)); // Capped
    }

    #[test]
    fn test_error_severity_mapping() {
        let transient = ClassifiedError::new(ErrorClass::Transient, "Test");
        assert_eq!(error_severity(&transient), ErrorSeverity::Warning);

        let terminal = ClassifiedError::new(ErrorClass::Terminal, "Test");
        assert_eq!(error_severity(&terminal), ErrorSeverity::Error);

        let user = ClassifiedError::new(ErrorClass::User, "Test");
        assert_eq!(error_severity(&user), ErrorSeverity::Info);
    }

    #[test]
    fn test_error_actions() {
        let transient = ClassifiedError::new(ErrorClass::Transient, "Test");
        let actions = error_actions(&transient);
        assert!(actions.contains(&ErrorAction::Retry));

        let terminal = ClassifiedError::new(ErrorClass::Terminal, "Test");
        let actions = error_actions(&terminal);
        assert!(actions.contains(&ErrorAction::CheckConfig));

        let user = ClassifiedError::new(ErrorClass::User, "Test");
        let actions = error_actions(&user);
        assert!(actions.contains(&ErrorAction::Dismiss));
    }

    #[test]
    fn test_parse_retry_after() {
        let data = Some(serde_json::json!({"retry_after": 15}));
        assert_eq!(parse_retry_after(&data), Duration::from_secs(15));

        let no_data = None;
        assert_eq!(parse_retry_after(&no_data), Duration::from_secs(10)); // Default
    }
}
