//! SSE (Server-Sent Events) Notifications for ACP Server
//!
//! This module provides the SSE notification infrastructure for streaming events
//! to connected ACP clients. It handles per-client subscriptions using broadcast
//! channels and provides translation from Dirigent protocol events to ACP notifications.
//!
//! ## Architecture
//!
//! The module is organized into several sub-modules:
//!
//! - `types` - Data structures for SSE notifications (SessionUpdateParams, SessionUpdateVariant, etc.)
//! - `notifier` - SseNotifier for managing client subscriptions and broadcasting
//! - `event_translator` - Translation from Dirigent Events to ACP notifications
//! - `content_transform` - Helper functions for content and metadata transformation
//!
//! ## SSE Wire Format
//!
//! The SSE stream uses the following format:
//!
//! ```text
//! event: session/update
//! data: {"sessionId": "...", "update": {"sessionUpdate": "agent_message_chunk", ...}}
//!
//! event: session/update
//! data: {"sessionId": "...", "update": {"sessionUpdate": "message_complete", ...}}
//! ```
//!
//! ## Example
//!
//! ```rust,ignore
//! use dirigent_acp_api::sse::{SseNotifier, SessionUpdateParams, translate_event};
//!
//! // Create the notifier
//! let notifier = SseNotifier::new();
//!
//! // Subscribe a client
//! let stream = notifier.subscribe("client-123");
//!
//! // Translate and broadcast an event
//! let updates = translate_event(&event);
//! for update in updates {
//!     notifier.broadcast("client-123", update);
//! }
//!
//! // Unsubscribe when done
//! notifier.unsubscribe("client-123");
//! ```

mod content_transform;
mod event_translator;
mod notifier;
mod types;

// Re-export public types
pub use content_transform::{
    extract_kind, rebuild_meta, tool_call_status_to_string, unwrap_tool_call_content,
};
pub use event_translator::translate_event;
pub use notifier::SseNotifier;
pub use types::{
    models_to_config_option, modes_to_config_option, AcpNotification, ConfigOption,
    ConfigOptionChoice, ConfigOptionType, SessionUpdateParams, SessionUpdateVariant, SlashCommand,
};

// ============================================================================
// Tests (moved from original sse.rs)
// ============================================================================

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

    // ========================================================================
    // AcpNotification Tests
    // ========================================================================

    #[test]
    fn test_message_chunk_serialization() {
        let notification = AcpNotification::MessageChunk {
            session_id: "sess-123".to_string(),
            message_id: "msg-456".to_string(),
            content: "Hello, world!".to_string(),
            content_type: Some("text".to_string()),
        };

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains(r#""type":"message_chunk"#));
        assert!(json.contains(r#""sessionId":"sess-123"#));
        assert!(json.contains(r#""messageId":"msg-456"#));
        assert!(json.contains(r#""content":"Hello, world!"#));
        assert!(json.contains(r#""contentType":"text"#));
    }

    #[test]
    fn test_message_complete_serialization() {
        let notification = AcpNotification::MessageComplete {
            session_id: "sess-123".to_string(),
            message_id: "msg-456".to_string(),
        };

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains(r#""type":"message_complete"#));
        assert!(json.contains(r#""sessionId":"sess-123"#));
        assert!(json.contains(r#""messageId":"msg-456"#));
    }

    #[test]
    fn test_session_idle_serialization() {
        let notification = AcpNotification::SessionIdle {
            session_id: "sess-123".to_string(),
        };

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains(r#""type":"session_idle"#));
        assert!(json.contains(r#""sessionId":"sess-123"#));
    }

    #[test]
    fn test_session_error_serialization() {
        let notification = AcpNotification::SessionError {
            session_id: "sess-123".to_string(),
            error: "Something went wrong".to_string(),
        };

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains(r#""type":"session_error"#));
        assert!(json.contains(r#""error":"Something went wrong"#));
    }

    #[test]
    fn test_tool_call_update_serialization() {
        let notification = AcpNotification::ToolCallUpdate {
            session_id: "sess-123".to_string(),
            message_id: "msg-456".to_string(),
            tool_call_id: "call-789".to_string(),
            tool_name: "bash".to_string(),
            status: "running".to_string(),
            title: Some("Running command".to_string()),
            error: None,
        };

        let json = serde_json::to_string(&notification).unwrap();
        assert!(json.contains(r#""type":"tool_call_update"#));
        assert!(json.contains(r#""toolName":"bash"#));
        assert!(json.contains(r#""status":"running"#));
        assert!(json.contains(r#""title":"Running command"#));
        // error should not be present when None
        assert!(!json.contains(r#""error""#));
    }

    #[test]
    fn test_event_type() {
        assert_eq!(
            AcpNotification::MessageChunk {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
                content: "c".to_string(),
                content_type: None,
            }
            .event_type(),
            "message/chunk"
        );

        assert_eq!(
            AcpNotification::MessageComplete {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
            }
            .event_type(),
            "message/complete"
        );

        assert_eq!(
            AcpNotification::SessionIdle {
                session_id: "s".to_string(),
            }
            .event_type(),
            "session/idle"
        );

        assert_eq!(
            AcpNotification::SessionError {
                session_id: "s".to_string(),
                error: "e".to_string(),
            }
            .event_type(),
            "session/error"
        );

        assert_eq!(
            AcpNotification::ToolCallUpdate {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
                tool_call_id: "t".to_string(),
                tool_name: "bash".to_string(),
                status: "running".to_string(),
                title: None,
                error: None,
            }
            .event_type(),
            "tool/update"
        );
    }

    #[test]
    fn test_to_sse_string() {
        let notification = AcpNotification::SessionIdle {
            session_id: "sess-123".to_string(),
        };

        let sse = notification.to_sse_string();
        assert!(sse.starts_with("event: session/idle\n"));
        assert!(sse.contains("data: "));
        assert!(sse.contains("sess-123"));
        assert!(sse.ends_with("\n\n"));
    }

    #[test]
    fn test_notification_roundtrip() {
        let notifications = vec![
            AcpNotification::MessageChunk {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
                content: "c".to_string(),
                content_type: Some("text".to_string()),
            },
            AcpNotification::MessageComplete {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
            },
            AcpNotification::SessionIdle {
                session_id: "s".to_string(),
            },
            AcpNotification::SessionError {
                session_id: "s".to_string(),
                error: "e".to_string(),
            },
            AcpNotification::ToolCallUpdate {
                session_id: "s".to_string(),
                message_id: "m".to_string(),
                tool_call_id: "t".to_string(),
                tool_name: "bash".to_string(),
                status: "running".to_string(),
                title: Some("title".to_string()),
                error: None,
            },
        ];

        for notification in notifications {
            let json = serde_json::to_string(&notification).unwrap();
            let deserialized: AcpNotification = serde_json::from_str(&json).unwrap();
            assert_eq!(notification, deserialized);
        }
    }

    // ========================================================================
    // SessionUpdate Tag Verification Tests
    // ========================================================================

    #[test]
    fn test_session_update_variant_uses_session_update_tag_tool_call() {
        // Test ToolCall variant with flattened structure
        let variant = SessionUpdateVariant::ToolCall {
            tool_call_id: "call_456".to_string(),
            title: Some("Running command".to_string()),
            kind: Some("command".to_string()),
            raw_input: Some(json!({"command": "ls"})),
            status: Some("in_progress".to_string()),
            content: vec![],
            _meta: Some(json!({
                "claudeCode": {
                    "toolName": "bash"
                }
            })),
        };

        let json = serde_json::to_value(&variant).unwrap();

        // Verify correct tag name "sessionUpdate" is present
        assert!(
            json.get("sessionUpdate").is_some(),
            "Missing 'sessionUpdate' field in JSON: {:?}",
            json
        );
        assert_eq!(
            json["sessionUpdate"], "tool_call",
            "Expected sessionUpdate value 'tool_call', got: {:?}",
            json["sessionUpdate"]
        );

        // Verify incorrect tag "type" is NOT present
        assert!(
            json.get("type").is_none(),
            "Should not have 'type' field in ACP output, found: {:?}",
            json.get("type")
        );

        // Verify flattened fields are present
        assert!(json.get("toolCallId").is_some(), "Missing toolCallId field");
        assert!(json.get("title").is_some(), "Missing title field");
        assert!(json.get("status").is_some(), "Missing status field");
    }

    #[test]
    fn test_session_update_variant_roundtrip_with_session_update_tag() {
        // Test that serialization and deserialization work correctly with sessionUpdate tag
        let variants = vec![
            SessionUpdateVariant::SessionIdle {},
            SessionUpdateVariant::MessageComplete {
                message_id: Some("msg_1".to_string()),
            },
            SessionUpdateVariant::AgentMessageChunk {
                content: dirigent_protocol::ContentBlock::Text {
                    text: "test".to_string(),
                },
            },
            SessionUpdateVariant::ToolCall {
                tool_call_id: "call_1".to_string(),
                title: None,
                kind: None,
                raw_input: None,
                status: Some("pending".to_string()),
                content: vec![],
                _meta: Some(json!({
                    "claudeCode": {
                        "toolName": "test_tool"
                    }
                })),
            },
        ];

        for variant in variants {
            // Serialize
            let json_str = serde_json::to_string(&variant).unwrap();
            let json_val: serde_json::Value = serde_json::from_str(&json_str).unwrap();

            // Verify sessionUpdate tag in JSON
            assert!(
                json_val.get("sessionUpdate").is_some(),
                "Missing sessionUpdate in serialized JSON for variant: {:?}",
                variant
            );

            // Verify no type tag
            assert!(
                json_val.get("type").is_none(),
                "Found unexpected 'type' tag for variant: {:?}",
                variant
            );

            // Deserialize back
            let deserialized: SessionUpdateVariant = serde_json::from_str(&json_str).unwrap();

            // Verify equality (roundtrip)
            assert_eq!(
                variant, deserialized,
                "Roundtrip failed for variant: {:?}",
                variant
            );
        }
    }

    // ========================================================================
    // camelCase Field Naming Compliance Tests
    // ========================================================================

    #[test]
    fn test_all_fields_use_camel_case() {
        // Test SessionUpdateParams with ToolCall variant
        let params = SessionUpdateParams {
            session_id: "sess_123".to_string(),
            update: SessionUpdateVariant::ToolCall {
                tool_call_id: "tool_123".to_string(),
                title: Some("test".to_string()),
                kind: Some("search".to_string()),
                raw_input: Some(json!({"key": "value"})),
                status: Some("pending".to_string()),
                content: vec![],
                _meta: None,
            },
            ..Default::default()
        };

        let json = serde_json::to_value(&params).unwrap();

        // Verify top-level camelCase
        assert!(
            json.get("sessionId").is_some(),
            "Should have sessionId (not session_id)"
        );
        assert!(
            json.get("session_id").is_none(),
            "Should NOT have session_id"
        );

        let update = &json["update"];

        // Verify ToolCall variant fields use camelCase
        assert!(
            update.get("toolCallId").is_some(),
            "Should have toolCallId (not tool_call_id)"
        );
        assert!(
            update.get("tool_call_id").is_none(),
            "Should NOT have tool_call_id"
        );
        assert!(
            update.get("rawInput").is_some(),
            "Should have rawInput (not raw_input)"
        );
        assert!(
            update.get("raw_input").is_none(),
            "Should NOT have raw_input"
        );
    }

    #[test]
    fn test_no_snake_case_in_serialized_json() {
        // Create various SessionUpdateParams with different variants
        let test_cases = vec![
            SessionUpdateParams {
                session_id: "s1".to_string(),
                update: SessionUpdateVariant::ToolCall {
                    tool_call_id: "tc1".to_string(),
                    title: Some("t".to_string()),
                    kind: Some("k".to_string()),
                    raw_input: Some(json!({})),
                    status: Some("pending".to_string()),
                    content: vec![],
                    _meta: None,
                },
                ..Default::default()
            },
            SessionUpdateParams {
                session_id: "s2".to_string(),
                update: SessionUpdateVariant::ToolCallUpdate {
                    tool_call_id: "tc2".to_string(),
                    status: Some("completed".to_string()),
                    content: vec![],
                    raw_output: Some(json!({})),
                    error: None,
                    _meta: None,
                },
                ..Default::default()
            },
            SessionUpdateParams {
                session_id: "s3".to_string(),
                update: SessionUpdateVariant::MessageComplete {
                    message_id: Some("m3".to_string()),
                },
                ..Default::default()
            },
        ];

        for params in test_cases {
            let json_str = serde_json::to_string(&params).unwrap();

            // Check for common snake_case fields that should NOT appear
            let forbidden_patterns = vec![
                "session_id",
                "tool_call_id",
                "raw_input",
                "raw_output",
                "message_id",
            ];

            for pattern in forbidden_patterns {
                assert!(
                    !json_str.contains(&format!("\"{}\"", pattern)),
                    "Found forbidden snake_case field '{}' in JSON: {}",
                    pattern,
                    json_str
                );
            }
        }
    }
}
