//! ACP (Agent-Client Protocol) Adapter
//!
//! This module provides translation from ACP JSON-RPC notifications to Dirigent protocol events.
//! It's designed to work with ACP-compliant agents that follow the JSON-RPC 2.0 notification pattern.
//!
//! # Architecture
//!
//! The adapter translates ACP notifications like:
//! - `session/update` - Session updates with various content types
//!
//! Into Dirigent protocol events that can be consumed by the UI and other components.
//!
//! # Supported Update Types
//!
//! - `agent_message_chunk` - Agent response content
//! - `user_message_chunk` - User message echo/history
//! - `thought_chunk` - Agent internal reasoning
//! - `tool_call` - Tool execution started
//! - `tool_result` - Tool execution completed

use crate::log_utils::format_for_log;
use crate::{ContentBlock, Event, SessionUpdate, ToolCall, ToolCallStatus, ToolCallContent};
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;

/// ACP notification translation error
#[derive(Debug, thiserror::Error)]
pub enum AcpTranslationError {
    /// Missing required field in notification
    #[error("Missing required field: {0}")]
    MissingField(String),

    /// Invalid value in notification
    #[error("Invalid value for {field}: {reason}")]
    InvalidValue { field: String, reason: String },

    /// Unknown notification method
    #[error("Unknown notification method: {0}")]
    UnknownMethod(String),

    /// Duplicate notification (already processed)
    #[error("Duplicate notification")]
    Duplicate,
}

/// Result type for ACP translation operations
pub type AcpResult<T> = Result<T, AcpTranslationError>;

/// ACP notification adapter
///
/// Translates ACP JSON-RPC notifications to Dirigent protocol events.
///
/// # State Management
///
/// The adapter maintains internal state for:
/// - Message tracking (to detect duplicates)
/// - Session metadata
/// - Tool execution state
///
/// # Thread Safety
///
/// AcpAdapter is designed to be used from a single task (the connector's event loop).
/// It uses Arc<RwLock> for state to support potential future multi-threaded scenarios.
#[derive(Debug)]
pub struct AcpAdapter {
    /// Processed notification IDs (for deduplication)
    processed_notifications: Arc<RwLock<HashMap<String, ()>>>,

    /// Active sessions (for tracking metadata - future use)
    sessions: Arc<RwLock<HashMap<String, SessionMetadata>>>,
}

/// Session metadata tracked by the adapter (for future use)
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct SessionMetadata {
    /// Session ID
    id: String,
    /// Session title (if available)
    title: Option<String>,
    /// Current working directory
    cwd: String,
    /// Model being used
    model: Option<String>,
}

impl AcpAdapter {
    /// Create a new ACP adapter
    pub fn new() -> Self {
        Self {
            processed_notifications: Arc::new(RwLock::new(HashMap::new())),
            sessions: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Translate an ACP notification to a Dirigent event
    ///
    /// # Arguments
    ///
    /// * `notification` - JSON-RPC notification object
    ///
    /// # Returns
    ///
    /// Dirigent Event or translation error. Returns `Err(Duplicate)` if the notification
    /// has already been processed (can be ignored).
    pub async fn translate_notification(&self, notification: Value) -> AcpResult<Event> {
        // Extract method
        let method = notification
            .get("method")
            .and_then(|m| m.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("method".to_string()))?;

        // Extract params
        let params = notification
            .get("params")
            .ok_or_else(|| AcpTranslationError::MissingField("params".to_string()))?;

        tracing::debug!(
            method = method,
            params = %format_for_log(params),
            "ACP notification received"
        );

        // Route based on method
        match method {
            "session/update" => self.translate_session_update(params).await,
            _ => {
                // Unknown method - log and skip
                tracing::warn!(method = method, "Unknown ACP notification method");
                Err(AcpTranslationError::UnknownMethod(method.to_string()))
            }
        }
    }

    /// Translate a session/update notification
    async fn translate_session_update(&self, params: &Value) -> AcpResult<Event> {
        // Extract session ID
        let session_id = params
            .get("sessionId")
            .and_then(|s| s.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("sessionId".to_string()))?
            .to_string();

        tracing::info!(session_id = %session_id, "🔍 Translating session/update notification");

        // Extract update object
        let update_obj = params
            .get("update")
            .and_then(|u| u.as_object())
            .ok_or_else(|| AcpTranslationError::MissingField("update".to_string()))?;

        // Determine update type - check both "type" and "sessionUpdate" fields for compatibility
        let update_type = update_obj
            .get("type")
            .or_else(|| update_obj.get("sessionUpdate"))
            .and_then(|t| t.as_str());

        // If no type field found, log the notification structure and skip it
        let update_type = match update_type {
            Some(t) => t,
            None => {
                tracing::warn!(
                    update_obj = ?update_obj,
                    "⚠️ session/update notification missing type field, skipping"
                );
                // Return a special error that can be handled gracefully
                return Err(AcpTranslationError::InvalidValue {
                    field: "update.type".to_string(),
                    reason: "Missing type field (might be a different notification variant)".to_string(),
                });
            }
        };

        tracing::info!(update_type = %update_type, "📝 Processing update type");

        // Route to variant-specific handler
        let session_update = match update_type {
            "agent_message_chunk" => self.translate_agent_message_chunk(update_obj)?,
            "user_message_chunk" => self.translate_user_message_chunk(update_obj)?,
            "thought_chunk" => self.translate_thought_chunk(update_obj)?,
            "tool_call" => self.translate_tool_call(update_obj)?,
            "tool_result" => self.translate_tool_result(update_obj)?,
            "tool_call_update" => self.translate_tool_call_update(update_obj)?,
            unknown => {
                tracing::warn!(
                    update_type = unknown,
                    "⚠️ Unknown session update type, forwarding as Unknown variant for pass-through"
                );
                // Forward unknown types as raw JSON for forward compatibility
                SessionUpdate::Unknown {
                    data: serde_json::Value::Object(update_obj.clone()),
                }
            }
        };

        tracing::info!(session_id = %session_id, "✅ Successfully translated session/update");

        // Note: connector_id will be set by the connector when emitting this event
        Ok(Event::SessionUpdate {
            connector_id: String::new(), // Placeholder - connector will set this
            session_id,
            update: session_update,
        })
    }

    /// Translate content block from ACP format to Dirigent format
    fn translate_content_block(&self, content: &Value) -> AcpResult<ContentBlock> {
        let content_type = content
            .get("type")
            .and_then(|t| t.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("content.type".to_string()))?;

        match content_type {
            "text" => {
                let text = content
                    .get("text")
                    .and_then(|t| t.as_str())
                    .ok_or_else(|| AcpTranslationError::MissingField("content.text".to_string()))?
                    .to_string();
                Ok(ContentBlock::Text { text })
            }
            unknown => {
                tracing::warn!(
                    content_type = unknown,
                    "Unknown content type, falling back to text representation"
                );
                // Fallback: represent unknown content as text
                Ok(ContentBlock::Text {
                    text: format!("[Unknown content type: {}]", serde_json::to_string(content).unwrap_or_default()),
                })
            }
        }
    }

    /// Translate content array from ACP format to ToolCallContent
    ///
    /// Handles both wrapped ACP format and backward compatibility with raw ContentBlock format.
    fn translate_tool_call_content(&self, content: &[Value]) -> Vec<ToolCallContent> {
        content
            .iter()
            .filter_map(|item| {
                // Try to parse as ToolCallContent (ACP format with wrapper)
                serde_json::from_value::<ToolCallContent>(item.clone())
                    .ok()
                    .or_else(|| {
                        // Fallback: try as ContentBlock (backward compatibility)
                        serde_json::from_value::<ContentBlock>(item.clone())
                            .ok()
                            .map(ToolCallContent::from_content_block)
                    })
            })
            .collect()
    }

    /// Translate agent_message_chunk update
    fn translate_agent_message_chunk(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        // messageId is optional - some agents (like Claude) don't send it in chunks
        // The connector will replace it with a Dirigent message_id anyway
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("tmp-{}", uuid::Uuid::new_v4()));

        let content = update
            .get("content")
            .ok_or_else(|| AcpTranslationError::MissingField("content".to_string()))?;

        let content = self.translate_content_block(content)?;

        Ok(SessionUpdate::AgentMessageChunk {
            message_id,
            content,
            _meta: None,
        })
    }

    /// Translate user_message_chunk update
    fn translate_user_message_chunk(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        // messageId is optional - some agents don't send it in chunks
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("tmp-{}", Uuid::new_v4()));

        let content = update
            .get("content")
            .ok_or_else(|| AcpTranslationError::MissingField("content".to_string()))?;

        let content = self.translate_content_block(content)?;

        Ok(SessionUpdate::UserMessageChunk {
            message_id,
            content,
            _meta: None,
        })
    }

    /// Translate thought_chunk update
    fn translate_thought_chunk(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        // messageId is optional - some agents don't send it in chunks
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("tmp-{}", Uuid::new_v4()));

        let content = update
            .get("content")
            .ok_or_else(|| AcpTranslationError::MissingField("content".to_string()))?;

        let content = self.translate_content_block(content)?;

        Ok(SessionUpdate::AgentThoughtChunk {
            message_id,
            content,
            _meta: None,
        })
    }

    /// Translate tool_call update
    ///
    /// Supports two formats:
    /// 1. Nested format (backward compatibility): `{ messageId, toolCall: {...} }`
    /// 2. Flat format (Claude): `{ toolCallId, status, title, rawInput, content, _meta }`
    fn translate_tool_call(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        // Make messageId optional - generate if missing (Claude doesn't send it)
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("tool-{}", Uuid::new_v4()));

        // Try nested toolCall format first (backward compatibility), then flat structure (Claude)
        let tool_call = if let Some(tool_call_obj) = update.get("toolCall").and_then(|t| t.as_object()) {
            // Original nested format
            self.translate_tool_call_object(tool_call_obj)?
        } else {
            // Claude's flat format - extract from top level
            self.translate_tool_call_flat(update)?
        };

        Ok(SessionUpdate::ToolCall {
            message_id,
            tool_call,
            _meta: None,
        })
    }

    /// Translate tool call from Claude's flat structure
    ///
    /// Claude sends tool calls with fields at the top level:
    /// ```json
    /// {
    ///   "sessionUpdate": "tool_call",
    ///   "toolCallId": "toolu_012...",
    ///   "title": "grep \"undefined\"",
    ///   "kind": "search",
    ///   "rawInput": {},
    ///   "status": "pending",
    ///   "content": [],
    ///   "_meta": {"claudeCode": {"toolName": "Grep"}}
    /// }
    /// ```
    fn translate_tool_call_flat(&self, update: &Map<String, Value>) -> AcpResult<ToolCall> {
        let id = update
            .get("toolCallId")
            .and_then(|i| i.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCallId".to_string()))?
            .to_string();

        // Get tool name from _meta.claudeCode.toolName, then kind, then title prefix, then "unknown"
        let tool_name = update
            .get("_meta")
            .and_then(|m| m.get("claudeCode"))
            .and_then(|c| c.get("toolName"))
            .and_then(|n| n.as_str())
            .or_else(|| update.get("kind").and_then(|k| k.as_str()))
            .or_else(|| {
                update
                    .get("title")
                    .and_then(|t| t.as_str())
                    .and_then(|t| t.split_whitespace().next())
            })
            .unwrap_or("unknown")
            .to_string();

        // Get status, defaulting to Pending if missing
        let status = update
            .get("status")
            .and_then(|s| s.as_str())
            .map(|s| self.translate_tool_status(s))
            .transpose()?
            .unwrap_or(ToolCallStatus::Pending);

        let title = update
            .get("title")
            .and_then(|t| t.as_str())
            .map(String::from);

        // Claude uses "rawInput" instead of "input"
        let raw_input = update.get("rawInput").cloned();

        // Translate content array if present
        let content = update
            .get("content")
            .and_then(|c| c.as_array())
            .map(|arr| self.translate_tool_call_content(arr))
            .unwrap_or_default();

        // Extract and store kind field in metadata for later reconstruction
        let metadata = if let Some(kind) = update.get("kind") {
            let mut meta_map = serde_json::Map::new();
            meta_map.insert("acp_kind".to_string(), kind.clone());
            Some(Value::Object(meta_map))
        } else {
            None
        };

        Ok(ToolCall {
            id,
            tool_name,
            status,
            content,
            raw_input,
            raw_output: None,
            title,
            error: None,
            metadata,
            origin: None, // Will be set by connector based on source
        })
    }

    /// Translate tool_result update
    ///
    /// **Note:** Claude Code does NOT use this notification type. Instead, it sends
    /// `tool_call_update` with `status: "completed"` to indicate tool completion.
    /// This handler is maintained for compatibility with other ACP-compliant agents
    /// that may follow the traditional tool_call → tool_result pattern.
    ///
    /// Expected format (traditional ACP):
    /// ```json
    /// {
    ///   "type": "tool_result",
    ///   "messageId": "msg-123",
    ///   "toolCallId": "call-456",
    ///   "result": {
    ///     "output": "...",
    ///     "error": "..."
    ///   }
    /// }
    /// ```
    ///
    /// See `translate_tool_call_update()` for Claude Code's actual completion mechanism.
    fn translate_tool_result(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("messageId".to_string()))?
            .to_string();

        let tool_call_id = update
            .get("toolCallId")
            .and_then(|t| t.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCallId".to_string()))?
            .to_string();

        let result_obj = update
            .get("result")
            .and_then(|r| r.as_object())
            .ok_or_else(|| AcpTranslationError::MissingField("result".to_string()))?;

        // Determine status based on error presence
        let status = if result_obj.contains_key("error") {
            ToolCallStatus::Error
        } else {
            ToolCallStatus::Completed
        };

        // Extract content from output and wrap in ToolCallContent
        let content = if let Some(output) = result_obj.get("output") {
            if let Some(text) = output.as_str() {
                vec![ToolCallContent::from_content_block(ContentBlock::Text {
                    text: text.to_string()
                })]
            } else {
                // Output is structured data - represent as JSON text
                vec![ToolCallContent::from_content_block(ContentBlock::Text {
                    text: serde_json::to_string_pretty(output).unwrap_or_default(),
                })]
            }
        } else {
            vec![]
        };

        let error = result_obj
            .get("error")
            .and_then(|e| e.as_str())
            .map(String::from);

        // Note: tool_name is not provided in result, UI will need to merge with original tool_call
        let tool_call = ToolCall {
            id: tool_call_id.clone(),
            tool_name: String::new(), // Will be populated by UI merging with original
            status,
            content,
            raw_input: None,
            raw_output: result_obj.get("output").cloned(),
            title: None,
            error,
            metadata: None,
            origin: None, // Will be set by connector based on source
        };

        Ok(SessionUpdate::ToolCallUpdate {
            message_id,
            tool_call_id,
            tool_call,
            _meta: None,
        })
    }

    /// Translate tool_call_update notification
    fn translate_tool_call_update(&self, update: &Map<String, Value>) -> AcpResult<SessionUpdate> {
        // Make messageId optional - generate if missing (Claude doesn't send it)
        let message_id = update
            .get("messageId")
            .and_then(|m| m.as_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| format!("tool-{}", Uuid::new_v4()));

        let tool_call_id = update
            .get("toolCallId")
            .and_then(|t| t.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCallId".to_string()))?
            .to_string();

        // Try nested toolCall format first (backward compatibility), then flat structure (Claude)
        let tool_call = if let Some(tool_call_obj) = update.get("toolCall").and_then(|t| t.as_object()) {
            // Original nested format
            self.translate_tool_call_object(tool_call_obj)?
        } else {
            // Claude's flat format - extract from top level
            self.translate_tool_call_update_flat(update)?
        };

        // Preserve metadata from _meta field (includes toolResponse for progress tracking)
        let _meta = update.get("_meta")
            .and_then(|m| serde_json::from_value(m.clone()).ok());

        Ok(SessionUpdate::ToolCallUpdate {
            message_id,
            tool_call_id,
            tool_call,
            _meta,
        })
    }

    /// Translate tool call update from Claude's flat structure
    ///
    /// Claude sends tool call updates with fields at the top level:
    /// ```json
    /// {
    ///   "sessionUpdate": "tool_call_update",
    ///   "toolCallId": "toolu_012...",
    ///   "status": "completed",
    ///   "content": [{"type": "content", "content": {"type": "text", "text": "..."}}],
    ///   "_meta": {"claudeCode": {"toolName": "Grep", "toolResponse": {...}}}
    /// }
    /// ```
    fn translate_tool_call_update_flat(&self, update: &Map<String, Value>) -> AcpResult<ToolCall> {
        let id = update
            .get("toolCallId")
            .and_then(|i| i.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCallId".to_string()))?
            .to_string();

        // Get tool name from _meta.claudeCode.toolName, then kind, then title prefix, then "unknown"
        let tool_name = update
            .get("_meta")
            .and_then(|m| m.get("claudeCode"))
            .and_then(|c| c.get("toolName"))
            .and_then(|n| n.as_str())
            .or_else(|| update.get("kind").and_then(|k| k.as_str()))
            .or_else(|| {
                update
                    .get("title")
                    .and_then(|t| t.as_str())
                    .and_then(|t| t.split_whitespace().next())
            })
            .unwrap_or("unknown")
            .to_string();

        // Get status, defaulting to Running if missing (updates typically have status)
        let status = update
            .get("status")
            .and_then(|s| s.as_str())
            .map(|s| self.translate_tool_status(s))
            .transpose()?
            .unwrap_or(ToolCallStatus::Running);

        let title = update
            .get("title")
            .and_then(|t| t.as_str())
            .map(String::from);

        // Claude uses "rawInput" instead of "input"
        let raw_input = update.get("rawInput").cloned();

        // Extract raw_output from toolResponse if present (for completed updates)
        let raw_output = update
            .get("_meta")
            .and_then(|m| m.get("claudeCode"))
            .and_then(|c| c.get("toolResponse"))
            .cloned();

        // Translate content array if present
        let content = update
            .get("content")
            .and_then(|c| c.as_array())
            .map(|arr| self.translate_tool_call_content(arr))
            .unwrap_or_default();

        Ok(ToolCall {
            id,
            tool_name,
            status,
            content,
            raw_input,
            raw_output,
            title,
            error: None,
            metadata: None,
            origin: None, // Will be set by connector based on source
        })
    }

    /// Translate a tool call object to ToolCall struct
    fn translate_tool_call_object(&self, obj: &Map<String, Value>) -> AcpResult<ToolCall> {
        let id = obj
            .get("id")
            .and_then(|i| i.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCall.id".to_string()))?
            .to_string();

        let tool_name = obj
            .get("name")
            .and_then(|n| n.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCall.name".to_string()))?
            .to_string();

        let status_str = obj
            .get("status")
            .and_then(|s| s.as_str())
            .ok_or_else(|| AcpTranslationError::MissingField("toolCall.status".to_string()))?;

        let status = self.translate_tool_status(status_str)?;

        let title = obj
            .get("title")
            .and_then(|t| t.as_str())
            .map(String::from);

        let raw_input = obj.get("input").cloned();

        Ok(ToolCall {
            id,
            tool_name,
            status,
            content: vec![], // Content comes in tool_result updates
            raw_input,
            raw_output: None,
            title,
            error: None,
            metadata: None,
            origin: None, // Will be set by connector based on source
        })
    }

    /// Translate tool status string to ToolCallStatus enum
    fn translate_tool_status(&self, status: &str) -> AcpResult<ToolCallStatus> {
        match status {
            "pending" => Ok(ToolCallStatus::Pending),
            "running" => Ok(ToolCallStatus::Running),
            "completed" => Ok(ToolCallStatus::Completed),
            "error" => Ok(ToolCallStatus::Error),
            unknown => Err(AcpTranslationError::InvalidValue {
                field: "status".to_string(),
                reason: format!("Unknown tool status: {}", unknown),
            }),
        }
    }

    /// Clear all adapter state
    ///
    /// Called when reconnecting to ensure clean state.
    pub async fn clear(&self) {
        self.processed_notifications.write().await.clear();
        self.sessions.write().await.clear();
    }
}

impl Default for AcpAdapter {
    fn default() -> Self {
        Self::new()
    }
}

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

    // ADAPT-001: Adapter creation test
    #[tokio::test]
    async fn test_adapter_creation() {
        let adapter = AcpAdapter::new();
        assert_eq!(adapter.processed_notifications.read().await.len(), 0);
        assert_eq!(adapter.sessions.read().await.len(), 0);
    }

    // ADAPT-002: Dispatcher tests
    #[tokio::test]
    async fn test_missing_method() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "params": {}
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::MissingField(_)
        ));
    }

    #[tokio::test]
    async fn test_missing_params() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update"
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::MissingField(_)
        ));
    }

    // ADAPT-015: Unknown method test
    #[tokio::test]
    async fn test_unknown_method() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "unknown/method",
            "params": {}
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::UnknownMethod(_)
        ));
    }

    // ADAPT-003, ADAPT-016: Session update tests with malformed params
    #[tokio::test]
    async fn test_session_update_missing_session_id() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "update": {
                    "type": "agent_message_chunk",
                    "messageId": "msg-1",
                    "content": {
                        "type": "text",
                        "text": "Hello"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::MissingField(_)
        ));
    }

    #[tokio::test]
    async fn test_session_update_missing_update_object() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-123"
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::MissingField(_)
        ));
    }

    #[tokio::test]
    async fn test_session_update_unknown_type() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-123",
                "update": {
                    "type": "unknown_type",
                    "messageId": "msg-1"
                }
            }
        });

        // Unknown types are now forwarded as SessionUpdate::Unknown for forward compatibility
        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { session_id, update, .. } => {
                assert_eq!(session_id, "session-123");
                match update {
                    SessionUpdate::Unknown { data } => {
                        // Verify the original data is preserved
                        assert_eq!(data.get("type").and_then(|v| v.as_str()), Some("unknown_type"));
                        assert_eq!(data.get("messageId").and_then(|v| v.as_str()), Some("msg-1"));
                    }
                    _ => panic!("Expected Unknown variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // ADAPT-004: Content block translation tests
    #[tokio::test]
    async fn test_translate_text_content() {
        let adapter = AcpAdapter::new();

        let content = json!({
            "type": "text",
            "text": "Hello, world!"
        });

        let result = adapter.translate_content_block(&content);
        assert!(result.is_ok());

        match result.unwrap() {
            ContentBlock::Text { text } => {
                assert_eq!(text, "Hello, world!");
            }
            _ => panic!("Expected Text content block"),
        }
    }

    #[tokio::test]
    async fn test_translate_unknown_content_type() {
        let adapter = AcpAdapter::new();

        let content = json!({
            "type": "image",
            "url": "https://example.com/image.png"
        });

        let result = adapter.translate_content_block(&content);
        assert!(result.is_ok());

        // Unknown types should fall back to text representation
        match result.unwrap() {
            ContentBlock::Text { text } => {
                assert!(text.contains("Unknown content type"));
            }
            _ => panic!("Expected Text content block fallback"),
        }
    }

    #[tokio::test]
    async fn test_translate_malformed_content() {
        let adapter = AcpAdapter::new();

        let content = json!({
            "text": "Missing type field"
        });

        let result = adapter.translate_content_block(&content);
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::MissingField(_)
        ));
    }

    // ADAPT-010: Agent message chunk tests
    #[tokio::test]
    async fn test_agent_message_chunk_translation() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-123",
                "update": {
                    "type": "agent_message_chunk",
                    "messageId": "msg-1",
                    "content": {
                        "type": "text",
                        "text": "Hello from agent"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-123");
                match update {
                    SessionUpdate::AgentMessageChunk { message_id, content, _meta } => {
                        assert_eq!(message_id, "msg-1");
                        match content {
                            ContentBlock::Text { text } => {
                                assert_eq!(text, "Hello from agent");
                            }
                            _ => panic!("Expected Text content"),
                        }
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected AgentMessageChunk"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_agent_message_chunk_missing_message_id() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-123",
                "update": {
                    "type": "agent_message_chunk",
                    "content": {
                        "type": "text",
                        "text": "Missing message ID"
                    }
                }
            }
        });

        // messageId is now optional - should succeed with generated ID
        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { session_id, update, .. } => {
                assert_eq!(session_id, "session-123");
                match update {
                    SessionUpdate::AgentMessageChunk { message_id, .. } => {
                        // Generated message ID should have "tmp-" prefix
                        assert!(message_id.starts_with("tmp-"));
                    }
                    _ => panic!("Expected AgentMessageChunk"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_agent_message_chunk_missing_content() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-123",
                "update": {
                    "type": "agent_message_chunk",
                    "messageId": "msg-1"
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
    }

    // ADAPT-011: User message chunk tests
    #[tokio::test]
    async fn test_user_message_chunk_translation() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-456",
                "update": {
                    "type": "user_message_chunk",
                    "messageId": "msg-2",
                    "content": {
                        "type": "text",
                        "text": "Hello from user"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-456");
                match update {
                    SessionUpdate::UserMessageChunk { message_id, content, _meta } => {
                        assert_eq!(message_id, "msg-2");
                        match content {
                            ContentBlock::Text { text } => {
                                assert_eq!(text, "Hello from user");
                            }
                            _ => panic!("Expected Text content"),
                        }
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected UserMessageChunk"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // ADAPT-012: Thought chunk tests
    #[tokio::test]
    async fn test_thought_chunk_translation() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-789",
                "update": {
                    "type": "thought_chunk",
                    "messageId": "msg-3",
                    "content": {
                        "type": "text",
                        "text": "Analyzing the problem..."
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-789");
                match update {
                    SessionUpdate::AgentThoughtChunk { message_id, content, _meta } => {
                        assert_eq!(message_id, "msg-3");
                        match content {
                            ContentBlock::Text { text } => {
                                assert_eq!(text, "Analyzing the problem...");
                            }
                            _ => panic!("Expected Text content"),
                        }
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected AgentThoughtChunk"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // ADAPT-013: Tool call translation tests
    #[tokio::test]
    async fn test_tool_call_translation_pending() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-abc",
                "update": {
                    "type": "tool_call",
                    "messageId": "msg-4",
                    "toolCall": {
                        "id": "call-1",
                        "name": "bash",
                        "status": "pending",
                        "title": "Run bash command",
                        "input": {
                            "command": "ls -la"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-abc");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, _meta } => {
                        assert_eq!(message_id, "msg-4");
                        assert_eq!(tool_call.id, "call-1");
                        assert_eq!(tool_call.tool_name, "bash");
                        assert_eq!(tool_call.status, ToolCallStatus::Pending);
                        assert_eq!(tool_call.title, Some("Run bash command".to_string()));
                        assert!(tool_call.raw_input.is_some());
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected ToolCall"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_call_translation_running() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-def",
                "update": {
                    "type": "tool_call",
                    "messageId": "msg-5",
                    "toolCall": {
                        "id": "call-2",
                        "name": "read_file",
                        "status": "running"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-def");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, .. } => {
                        assert_eq!(message_id, "msg-5");
                        assert_eq!(tool_call.id, "call-2");
                        assert_eq!(tool_call.status, ToolCallStatus::Running);
                        assert!(tool_call.title.is_none());
                        assert!(tool_call.raw_input.is_none());
                    }
                    _ => panic!("Expected ToolCall"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_call_missing_fields() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-ghi",
                "update": {
                    "type": "tool_call",
                    "messageId": "msg-6",
                    "toolCall": {
                        "id": "call-3"
                        // Missing name and status
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_tool_call_invalid_status() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-jkl",
                "update": {
                    "type": "tool_call",
                    "messageId": "msg-7",
                    "toolCall": {
                        "id": "call-4",
                        "name": "test",
                        "status": "invalid_status"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::InvalidValue { .. }
        ));
    }

    // ADAPT-019: Claude flat format tool_call tests
    #[tokio::test]
    async fn test_tool_call_claude_flat_format() {
        let adapter = AcpAdapter::new();

        // Claude's actual notification format: flat structure, no messageId
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-claude",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_012TKJvVXSYFFARfUWj9okv8",
                    "title": "grep \"undefined\"",
                    "kind": "search",
                    "rawInput": {
                        "pattern": "undefined",
                        "path": "."
                    },
                    "status": "pending",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-claude");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, _meta } => {
                        // Generated message ID should have "tool-" prefix
                        assert!(message_id.starts_with("tool-"));
                        assert_eq!(tool_call.id, "toolu_012TKJvVXSYFFARfUWj9okv8");
                        assert_eq!(tool_call.tool_name, "Grep");
                        assert_eq!(tool_call.status, ToolCallStatus::Pending);
                        assert_eq!(tool_call.title, Some("grep \"undefined\"".to_string()));
                        assert!(tool_call.raw_input.is_some());
                        assert!(_meta.is_none());

                        // T001: Verify kind field is stored in metadata
                        assert!(tool_call.metadata.is_some(), "Metadata should be present when kind field exists");
                        if let Some(metadata) = tool_call.metadata {
                            assert_eq!(
                                metadata.get("acp_kind"),
                                Some(&json!("search")),
                                "kind field should be stored as 'acp_kind' in metadata"
                            );
                        }
                    }
                    _ => panic!("Expected ToolCall"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_call_claude_flat_format_without_tool_name() {
        let adapter = AcpAdapter::new();

        // Claude format without _meta.claudeCode.toolName - should fall back to "unknown"
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-claude-2",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_123456",
                    "status": "running",
                    "content": []
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-claude-2");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, .. } => {
                        assert!(message_id.starts_with("tool-"));
                        assert_eq!(tool_call.id, "toolu_123456");
                        assert_eq!(tool_call.tool_name, "unknown");
                        assert_eq!(tool_call.status, ToolCallStatus::Running);
                        assert!(tool_call.title.is_none());

                        // T001: Verify that missing kind field doesn't cause errors
                        assert!(tool_call.metadata.is_none(), "Metadata should be None when kind field is missing");
                    }
                    _ => panic!("Expected ToolCall"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_call_claude_flat_format_with_content() {
        let adapter = AcpAdapter::new();

        // Claude format with content array (wrapped format)
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-claude-3",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_789",
                    "status": "completed",
                    "content": [
                        {
                            "type": "content",
                            "content": {
                                "type": "text",
                                "text": "Search results here"
                            }
                        }
                    ],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-claude-3");
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        assert_eq!(tool_call.id, "toolu_789");
                        assert_eq!(tool_call.tool_name, "Grep");
                        assert_eq!(tool_call.status, ToolCallStatus::Completed);
                        assert_eq!(tool_call.content.len(), 1);
                        match &tool_call.content[0] {
                            ToolCallContent::Content { content } => match content {
                                ContentBlock::Text { text } => {
                                    assert_eq!(text, "Search results here");
                                }
                                _ => panic!("Expected Text content"),
                            }
                            _ => panic!("Expected Content wrapper"),
                        }
                    }
                    _ => panic!("Expected ToolCall"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // ADAPT-014: Tool result translation tests
    #[tokio::test]
    async fn test_tool_result_translation_completed() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-mno",
                "update": {
                    "type": "tool_result",
                    "messageId": "msg-8",
                    "toolCallId": "call-5",
                    "result": {
                        "output": "Command completed successfully"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-mno");
                match update {
                    SessionUpdate::ToolCallUpdate { message_id, tool_call_id, tool_call, _meta } => {
                        assert_eq!(message_id, "msg-8");
                        assert_eq!(tool_call_id, "call-5");
                        assert_eq!(tool_call.id, "call-5");
                        assert_eq!(tool_call.status, ToolCallStatus::Completed);
                        assert_eq!(tool_call.content.len(), 1);
                        match &tool_call.content[0] {
                            ToolCallContent::Content { content } => match content {
                                ContentBlock::Text { text } => {
                                    assert_eq!(text, "Command completed successfully");
                                }
                                _ => panic!("Expected Text content"),
                            }
                            _ => panic!("Expected Content wrapper"),
                        }
                        assert!(tool_call.error.is_none());
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected ToolCallUpdate"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_result_translation_error() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-pqr",
                "update": {
                    "type": "tool_result",
                    "messageId": "msg-9",
                    "toolCallId": "call-6",
                    "result": {
                        "error": "Permission denied"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-pqr");
                match update {
                    SessionUpdate::ToolCallUpdate { message_id, tool_call_id, tool_call, .. } => {
                        assert_eq!(message_id, "msg-9");
                        assert_eq!(tool_call_id, "call-6");
                        assert_eq!(tool_call.status, ToolCallStatus::Error);
                        assert_eq!(tool_call.error, Some("Permission denied".to_string()));
                    }
                    _ => panic!("Expected ToolCallUpdate"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_result_with_structured_output() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-stu",
                "update": {
                    "type": "tool_result",
                    "messageId": "msg-10",
                    "toolCallId": "call-7",
                    "result": {
                        "output": {
                            "files": ["file1.txt", "file2.txt"],
                            "count": 2
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-stu");
                match update {
                    SessionUpdate::ToolCallUpdate { tool_call, .. } => {
                        assert_eq!(tool_call.status, ToolCallStatus::Completed);
                        assert_eq!(tool_call.content.len(), 1);
                        // Structured output should be converted to pretty JSON text
                        match &tool_call.content[0] {
                            ToolCallContent::Content { content } => match content {
                                ContentBlock::Text { text } => {
                                    assert!(text.contains("files"));
                                    assert!(text.contains("file1.txt"));
                                }
                                _ => panic!("Expected Text content"),
                            }
                            _ => panic!("Expected Content wrapper"),
                        }
                    }
                    _ => panic!("Expected ToolCallUpdate"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_tool_result_missing_fields() {
        let adapter = AcpAdapter::new();

        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-vwx",
                "update": {
                    "type": "tool_result",
                    "messageId": "msg-11"
                    // Missing toolCallId and result
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
    }

    // Adapter state management tests
    #[tokio::test]
    async fn test_adapter_clear() {
        let adapter = AcpAdapter::new();

        // Add some state
        adapter
            .processed_notifications
            .write()
            .await
            .insert("notif-1".to_string(), ());

        assert_eq!(adapter.processed_notifications.read().await.len(), 1);

        // Clear
        adapter.clear().await;

        assert_eq!(adapter.processed_notifications.read().await.len(), 0);
    }

    // Tool status translation tests
    #[tokio::test]
    async fn test_translate_tool_status() {
        let adapter = AcpAdapter::new();

        assert_eq!(adapter.translate_tool_status("pending").unwrap(), ToolCallStatus::Pending);
        assert_eq!(adapter.translate_tool_status("running").unwrap(), ToolCallStatus::Running);
        assert_eq!(adapter.translate_tool_status("completed").unwrap(), ToolCallStatus::Completed);
        assert_eq!(adapter.translate_tool_status("error").unwrap(), ToolCallStatus::Error);

        let result = adapter.translate_tool_status("invalid");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::InvalidValue { .. }
        ));
    }

    // ADAPT-017: Missing type field handling
    #[tokio::test]
    async fn test_session_update_missing_type_field() {
        let adapter = AcpAdapter::new();

        // Notification without type field (like some Claude Code notifications)
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-xyz",
                "update": {
                    "someOtherField": "someValue"
                    // Missing "type" field
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            AcpTranslationError::InvalidValue { .. }
        ));
    }

    // ADAPT-018: Alternative type field name (sessionUpdate)
    #[tokio::test]
    async fn test_session_update_with_session_update_field() {
        let adapter = AcpAdapter::new();

        // Notification with "sessionUpdate" instead of "type"
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "session-abc",
                "update": {
                    "sessionUpdate": "agent_message_chunk",
                    "messageId": "msg-1",
                    "content": {
                        "type": "text",
                        "text": "Hello"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "session-abc");
                match update {
                    SessionUpdate::AgentMessageChunk { message_id, .. } => {
                        assert_eq!(message_id, "msg-1");
                    }
                    _ => panic!("Expected AgentMessageChunk"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // T007: Test translate_tool_call() without messageId (Claude format)
    // Tests Claude's flat format with no messageId and _meta.claudeCode.toolName
    #[tokio::test]
    async fn test_t007_translate_tool_call_without_message_id_claude_format() {
        let adapter = AcpAdapter::new();

        // Claude's format: flat structure, no messageId, tool name in _meta
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t007",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_test_007",
                    "status": "pending",
                    "title": "grep \"test pattern\"",
                    "kind": "search",
                    "rawInput": {
                        "pattern": "test pattern",
                        "path": "/test/path"
                    },
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Translation should succeed for Claude format");

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "test-session-t007");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, _meta } => {
                        // T007: Verify generated message ID format matches tool-{UUID} pattern
                        assert!(
                            message_id.starts_with("tool-"),
                            "Generated message ID should start with 'tool-', got: {}",
                            message_id
                        );
                        assert_eq!(
                            message_id.len(),
                            41, // "tool-" (5) + UUID (36)
                            "Message ID should be 'tool-' + UUID format"
                        );

                        // Verify tool call details
                        assert_eq!(tool_call.id, "toolu_test_007");
                        assert_eq!(tool_call.tool_name, "Grep");
                        assert_eq!(tool_call.status, ToolCallStatus::Pending);
                        assert_eq!(tool_call.title, Some("grep \"test pattern\"".to_string()));
                        assert!(tool_call.raw_input.is_some());
                        assert_eq!(tool_call.content.len(), 0);
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t007_tool_call_different_statuses_no_message_id() {
        let adapter = AcpAdapter::new();

        // Test with "running" status
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t007-running",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_running",
                    "status": "running",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Read"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        assert_eq!(tool_call.status, ToolCallStatus::Running);
                        assert_eq!(tool_call.tool_name, "Read");
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // T008: Test translate_tool_call() with messageId (backward compatibility)
    // Tests original nested format with explicit messageId
    #[tokio::test]
    async fn test_t008_translate_tool_call_with_message_id_nested_format() {
        let adapter = AcpAdapter::new();

        // Original nested format with explicit messageId
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t008",
                "update": {
                    "type": "tool_call",
                    "messageId": "explicit-msg-id-123",
                    "toolCall": {
                        "id": "tool-nested-001",
                        "name": "bash",
                        "status": "pending",
                        "title": "Run command",
                        "input": {
                            "command": "ls -la"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Translation should succeed for nested format");

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "test-session-t008");
                match update {
                    SessionUpdate::ToolCall { message_id, tool_call, _meta } => {
                        // T008: Verify explicit messageId is preserved
                        assert_eq!(message_id, "explicit-msg-id-123");

                        // Verify tool call details from nested format
                        assert_eq!(tool_call.id, "tool-nested-001");
                        assert_eq!(tool_call.tool_name, "bash");
                        assert_eq!(tool_call.status, ToolCallStatus::Pending);
                        assert_eq!(tool_call.title, Some("Run command".to_string()));
                        assert!(tool_call.raw_input.is_some());
                        assert!(_meta.is_none());
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t008_nested_format_all_statuses() {
        let adapter = AcpAdapter::new();

        // Test all status values with nested format
        let statuses = vec![
            ("pending", ToolCallStatus::Pending),
            ("running", ToolCallStatus::Running),
            ("completed", ToolCallStatus::Completed),
            ("error", ToolCallStatus::Error),
        ];

        for (status_str, expected_status) in statuses {
            let notification = json!({
                "jsonrpc": "2.0",
                "method": "session/update",
                "params": {
                    "sessionId": "test-session",
                    "update": {
                        "type": "tool_call",
                        "messageId": format!("msg-{}", status_str),
                        "toolCall": {
                            "id": format!("tool-{}", status_str),
                            "name": "test_tool",
                            "status": status_str
                        }
                    }
                }
            });

            let result = adapter.translate_notification(notification).await;
            assert!(result.is_ok(), "Failed for status: {}", status_str);

            match result.unwrap() {
                Event::SessionUpdate { update, .. } => {
                    match update {
                        SessionUpdate::ToolCall { tool_call, .. } => {
                            assert_eq!(tool_call.status, expected_status, "Wrong status for: {}", status_str);
                        }
                        _ => panic!("Expected ToolCall variant"),
                    }
                }
                _ => panic!("Expected SessionUpdate event"),
            }
        }
    }

    // T009: Test translate_tool_call_update() Claude format
    // Tests status transitions, content updates, and metadata preservation
    #[tokio::test]
    async fn test_t009_tool_call_update_pending_to_running() {
        let adapter = AcpAdapter::new();

        // Update: pending → running
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t009-running",
                "update": {
                    "sessionUpdate": "tool_call_update",
                    "toolCallId": "toolu_t009",
                    "status": "running",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Bash",
                            "toolResponse": {
                                "started": true
                            }
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Tool call update should succeed");

        match result.unwrap() {
            Event::SessionUpdate { connector_id: _, session_id, update } => {
                assert_eq!(session_id, "test-session-t009-running");
                match update {
                    SessionUpdate::ToolCallUpdate { message_id, tool_call_id, tool_call, _meta } => {
                        // T009: Verify generated message ID
                        assert!(message_id.starts_with("tool-"));
                        assert_eq!(tool_call_id, "toolu_t009");
                        assert_eq!(tool_call.id, "toolu_t009");
                        assert_eq!(tool_call.status, ToolCallStatus::Running);
                        assert_eq!(tool_call.tool_name, "Bash");

                        // T009: Verify metadata preservation from _meta.claudeCode.toolResponse
                        assert!(_meta.is_some(), "Metadata should be preserved");
                    }
                    _ => panic!("Expected ToolCallUpdate variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t009_tool_call_update_running_to_completed() {
        let adapter = AcpAdapter::new();

        // Update: running → completed with content
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t009-completed",
                "update": {
                    "sessionUpdate": "tool_call_update",
                    "toolCallId": "toolu_t009_complete",
                    "status": "completed",
                    "content": [
                        {
                            "type": "content",
                            "content": {
                                "type": "text",
                                "text": "Tool execution completed successfully"
                            }
                        },
                        {
                            "type": "content",
                            "content": {
                                "type": "text",
                                "text": "Output: test result"
                            }
                        }
                    ],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep",
                            "toolResponse": {
                                "exitCode": 0,
                                "duration": 125
                            }
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCallUpdate { tool_call, _meta, .. } => {
                        // T009: Test status transition to completed
                        assert_eq!(tool_call.status, ToolCallStatus::Completed);

                        // T009: Test content updates (Claude wraps content blocks)
                        assert_eq!(tool_call.content.len(), 2, "Should have 2 content blocks");
                        match &tool_call.content[0] {
                            ToolCallContent::Content { content } => match content {
                                ContentBlock::Text { text } => {
                                    assert_eq!(text, "Tool execution completed successfully");
                                }
                                _ => panic!("Expected Text content"),
                            }
                            _ => panic!("Expected Content wrapper"),
                        }
                        match &tool_call.content[1] {
                            ToolCallContent::Content { content } => match content {
                                ContentBlock::Text { text } => {
                                    assert_eq!(text, "Output: test result");
                                }
                                _ => panic!("Expected Text content"),
                            }
                            _ => panic!("Expected Content wrapper"),
                        }

                        // T009: Verify metadata preservation
                        assert!(_meta.is_some());
                        if let Some(meta) = _meta {
                            let meta_obj = serde_json::to_value(&meta).unwrap();
                            assert!(meta_obj.get("claudeCode").is_some());
                        }
                    }
                    _ => panic!("Expected ToolCallUpdate variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t009_tool_call_update_error_status() {
        let adapter = AcpAdapter::new();

        // Update: error status
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t009-error",
                "update": {
                    "sessionUpdate": "tool_call_update",
                    "toolCallId": "toolu_t009_error",
                    "status": "error",
                    "content": [
                        {
                            "type": "content",
                            "content": {
                                "type": "text",
                                "text": "Error: File not found"
                            }
                        }
                    ],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Read",
                            "toolResponse": {
                                "error": "ENOENT"
                            }
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCallUpdate { tool_call, _meta, .. } => {
                        assert_eq!(tool_call.status, ToolCallStatus::Error);
                        assert_eq!(tool_call.tool_name, "Read");
                        assert_eq!(tool_call.content.len(), 1);

                        // Verify metadata preserved for error case
                        assert!(_meta.is_some());
                    }
                    _ => panic!("Expected ToolCallUpdate variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t009_tool_call_update_with_tool_response_data() {
        let adapter = AcpAdapter::new();

        // Test that toolResponse data in _meta.claudeCode is preserved and extracted
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t009-response",
                "update": {
                    "sessionUpdate": "tool_call_update",
                    "toolCallId": "toolu_t009_response",
                    "status": "completed",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Edit",
                            "toolResponse": {
                                "linesAdded": 5,
                                "linesRemoved": 2,
                                "filePath": "/test/file.rs"
                            }
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok());

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCallUpdate { tool_call, _meta, .. } => {
                        assert_eq!(tool_call.status, ToolCallStatus::Completed);

                        // T009: Verify raw_output extracted from toolResponse
                        assert!(
                            tool_call.raw_output.is_some(),
                            "raw_output should be extracted from toolResponse"
                        );

                        if let Some(output) = tool_call.raw_output {
                            assert_eq!(output.get("linesAdded"), Some(&json!(5)));
                            assert_eq!(output.get("linesRemoved"), Some(&json!(2)));
                            assert_eq!(output.get("filePath"), Some(&json!("/test/file.rs")));
                        }

                        // Metadata should also be preserved
                        assert!(_meta.is_some());
                    }
                    _ => panic!("Expected ToolCallUpdate variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    // T010: Test edge cases
    // Missing toolCallId, missing status, missing tool name, empty content array
    #[tokio::test]
    async fn test_t010_missing_tool_call_id_should_error() {
        let adapter = AcpAdapter::new();

        // Missing toolCallId - should error
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-error",
                "update": {
                    "sessionUpdate": "tool_call",
                    "status": "pending",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err(), "Should error when toolCallId is missing");

        match result.unwrap_err() {
            AcpTranslationError::MissingField(field) => {
                assert_eq!(field, "toolCallId");
            }
            _ => panic!("Expected MissingField error"),
        }
    }

    #[tokio::test]
    async fn test_t010_missing_status_defaults_to_pending() {
        let adapter = AcpAdapter::new();

        // Missing status - should default to Pending for tool_call
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-default",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_t010_default",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Bash"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Should succeed with default status");

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        // T010: Verify status defaults to Pending
                        assert_eq!(
                            tool_call.status,
                            ToolCallStatus::Pending,
                            "Status should default to Pending when missing"
                        );
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t010_missing_status_in_update_defaults_to_running() {
        let adapter = AcpAdapter::new();

        // Missing status in tool_call_update - should default to Running
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-update-default",
                "update": {
                    "sessionUpdate": "tool_call_update",
                    "toolCallId": "toolu_t010_update_default",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Read"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Should succeed with default status");

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCallUpdate { tool_call, .. } => {
                        // T010: Verify status defaults to Running for updates
                        assert_eq!(
                            tool_call.status,
                            ToolCallStatus::Running,
                            "Status should default to Running when missing in update"
                        );
                    }
                    _ => panic!("Expected ToolCallUpdate variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t010_missing_tool_name_uses_unknown() {
        let adapter = AcpAdapter::new();

        // Missing _meta.claudeCode.toolName - should use "unknown"
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-unknown",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_t010_unknown",
                    "status": "pending",
                    "content": []
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Should succeed with unknown tool name");

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        // T010: Verify tool name defaults to "unknown"
                        assert_eq!(
                            tool_call.tool_name,
                            "unknown",
                            "Tool name should be 'unknown' when _meta.claudeCode.toolName is missing"
                        );
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t010_empty_content_array() {
        let adapter = AcpAdapter::new();

        // Empty content array - should succeed with empty vec
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-empty-content",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_t010_empty",
                    "status": "pending",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Should succeed with empty content array");

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        // T010: Verify empty content array is handled
                        assert_eq!(
                            tool_call.content.len(),
                            0,
                            "Content should be empty vec when content array is empty"
                        );
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t010_partial_meta_structure() {
        let adapter = AcpAdapter::new();

        // Partial _meta structure (has _meta but no claudeCode)
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-partial-meta",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_t010_partial",
                    "status": "running",
                    "content": [],
                    "_meta": {
                        "someOtherField": "value"
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_ok(), "Should succeed with partial _meta");

        match result.unwrap() {
            Event::SessionUpdate { update, .. } => {
                match update {
                    SessionUpdate::ToolCall { tool_call, .. } => {
                        // T010: Should fall back to "unknown" when claudeCode is missing
                        assert_eq!(tool_call.tool_name, "unknown");
                    }
                    _ => panic!("Expected ToolCall variant"),
                }
            }
            _ => panic!("Expected SessionUpdate event"),
        }
    }

    #[tokio::test]
    async fn test_t010_invalid_status_value() {
        let adapter = AcpAdapter::new();

        // Invalid status value - should error
        let notification = json!({
            "jsonrpc": "2.0",
            "method": "session/update",
            "params": {
                "sessionId": "test-session-t010-invalid-status",
                "update": {
                    "sessionUpdate": "tool_call",
                    "toolCallId": "toolu_t010_invalid",
                    "status": "invalid_status_value",
                    "content": [],
                    "_meta": {
                        "claudeCode": {
                            "toolName": "Grep"
                        }
                    }
                }
            }
        });

        let result = adapter.translate_notification(notification).await;
        assert!(result.is_err(), "Should error for invalid status value");

        match result.unwrap_err() {
            AcpTranslationError::InvalidValue { field, .. } => {
                assert_eq!(field, "status");
            }
            _ => panic!("Expected InvalidValue error"),
        }
    }
}
