//! Echo mode implementation for the Gateway connector
//!
//! This module handles the echo response generation and optional
//! streaming simulation for realistic response behavior.

use crate::sharing::bus::SharingBus;
use dirigent_protocol::types::ContentBlock;
use dirigent_protocol::{Event, Message, MessageRole, MessageStatus, SessionUpdate};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::broadcast;
use tracing::debug;
use uuid::Uuid;

/// Configuration for echo behavior
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EchoConfig {
    /// Whether to simulate streaming by breaking the response into chunks
    #[serde(default)]
    pub simulate_streaming: bool,

    /// Delay between chunks in milliseconds (when simulate_streaming is true)
    /// Default: 0 (instant)
    #[serde(default)]
    pub chunk_delay_ms: u64,

    /// Approximate size of each chunk in characters (when simulate_streaming is true)
    /// Default: 10
    #[serde(default = "default_chunk_size")]
    pub chunk_size: usize,

    /// Prefix to add to echoed messages (optional)
    #[serde(default)]
    pub prefix: Option<String>,

    /// Suffix to add to echoed messages (optional)
    #[serde(default)]
    pub suffix: Option<String>,
}

fn default_chunk_size() -> usize {
    10
}

impl Default for EchoConfig {
    fn default() -> Self {
        Self {
            simulate_streaming: false,
            chunk_delay_ms: 0,
            chunk_size: default_chunk_size(),
            prefix: None,
            suffix: None,
        }
    }
}

/// Generate an echo response for the given input text
///
/// # Arguments
///
/// * `text` - The user's message text to echo
/// * `config` - Echo configuration for formatting
///
/// # Returns
///
/// The formatted echo response string
pub fn generate_echo_response(text: &str, config: &EchoConfig) -> String {
    let mut response = String::new();

    if let Some(ref prefix) = config.prefix {
        response.push_str(prefix);
    }

    response.push_str(text);

    if let Some(ref suffix) = config.suffix {
        response.push_str(suffix);
    }

    response
}

/// Stream an echo response as multiple chunks
///
/// This function simulates streaming behavior by breaking the response
/// into chunks and emitting them with optional delays.
///
/// # Arguments
///
/// * `connector_id` - The connector ID for events
/// * `session_id` - The session ID for events
/// * `message_id` - The message ID for the response
/// * `response` - The full response text to stream
/// * `config` - Echo configuration for chunk sizes and delays
/// * `events_tx` - The broadcast sender for emitting events
pub async fn stream_echo_response(
    connector_id: &str,
    connector_uid: Option<Uuid>,
    session_id: &str,
    message_id: &str,
    response: &str,
    config: &EchoConfig,
    events_tx: &broadcast::Sender<Event>,
    sharing_bus: &Arc<SharingBus>,
) {
    debug!(
        connector_id = %connector_id,
        session_id = %session_id,
        message_id = %message_id,
        response_len = response.len(),
        chunk_size = config.chunk_size,
        delay_ms = config.chunk_delay_ms,
        "Streaming echo response"
    );

    // Create a placeholder message for MessageStarted
    let placeholder_message = Message {
        id: message_id.to_string(),
        session_id: session_id.to_string(),
        role: MessageRole::Assistant,
        content: vec![],
        created_at: chrono::Utc::now(),
        status: MessageStatus::Streaming,
        metadata: None,
    };

    // Emit MessageStarted (bus + broadcast)
    let started_event = Event::MessageStarted {
        connector_id: connector_id.to_string(),
        message: placeholder_message,
    };
    let bus_event = dirigent_protocol::streaming::BusEvent::from_connector_event(
        started_event.clone(),
        connector_uid,
        connector_id.to_string(),
    );
    sharing_bus.publish(bus_event).await;
    let _ = events_tx.send(started_event);

    // Break response into chunks and stream them
    let chunks = break_into_chunks(response, config.chunk_size);

    for chunk in chunks {
        // Emit the chunk (bus + broadcast)
        let chunk_event = Event::SessionUpdate {
            connector_id: connector_id.to_string(),
            session_id: session_id.to_string(),
            update: SessionUpdate::AgentMessageChunk {
                message_id: message_id.to_string(),
                content: ContentBlock::Text { text: chunk },
                _meta: None,
            },
        };
        let bus_event = dirigent_protocol::streaming::BusEvent::from_connector_event(
            chunk_event.clone(),
            connector_uid,
            connector_id.to_string(),
        );
        sharing_bus.publish(bus_event).await;
        let _ = events_tx.send(chunk_event);

        // Apply delay if configured
        if config.chunk_delay_ms > 0 {
            tokio::time::sleep(tokio::time::Duration::from_millis(config.chunk_delay_ms)).await;
        }
    }

    debug!(
        connector_id = %connector_id,
        message_id = %message_id,
        "Finished streaming echo response"
    );
}

/// Break a string into chunks of approximately the given size
///
/// Tries to break at word boundaries when possible.
fn break_into_chunks(text: &str, chunk_size: usize) -> Vec<String> {
    if chunk_size == 0 || text.is_empty() {
        return vec![text.to_string()];
    }

    let mut chunks = Vec::new();
    let mut current_chunk = String::new();

    for word in text.split_inclusive(char::is_whitespace) {
        if current_chunk.len() + word.len() > chunk_size && !current_chunk.is_empty() {
            chunks.push(current_chunk);
            current_chunk = String::new();
        }
        current_chunk.push_str(word);
    }

    if !current_chunk.is_empty() {
        chunks.push(current_chunk);
    }

    // If no chunks were created (e.g., single long word), just return the whole text
    if chunks.is_empty() {
        chunks.push(text.to_string());
    }

    chunks
}

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

    #[test]
    fn test_echo_config_default() {
        let config = EchoConfig::default();
        assert!(!config.simulate_streaming);
        assert_eq!(config.chunk_delay_ms, 0);
        assert_eq!(config.chunk_size, 10);
        assert!(config.prefix.is_none());
        assert!(config.suffix.is_none());
    }

    #[test]
    fn test_echo_config_serialization() {
        let config = EchoConfig {
            simulate_streaming: true,
            chunk_delay_ms: 50,
            chunk_size: 20,
            prefix: Some("Echo: ".to_string()),
            suffix: None,
        };

        let json = serde_json::to_string(&config).unwrap();
        let deserialized: EchoConfig = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.simulate_streaming, config.simulate_streaming);
        assert_eq!(deserialized.chunk_delay_ms, config.chunk_delay_ms);
        assert_eq!(deserialized.chunk_size, config.chunk_size);
        assert_eq!(deserialized.prefix, config.prefix);
    }

    #[test]
    fn test_generate_echo_response_simple() {
        let config = EchoConfig::default();
        let response = generate_echo_response("Hello, World!", &config);
        assert_eq!(response, "Hello, World!");
    }

    #[test]
    fn test_generate_echo_response_with_prefix() {
        let config = EchoConfig {
            prefix: Some("Echo: ".to_string()),
            ..Default::default()
        };
        let response = generate_echo_response("Hello", &config);
        assert_eq!(response, "Echo: Hello");
    }

    #[test]
    fn test_generate_echo_response_with_suffix() {
        let config = EchoConfig {
            suffix: Some(" [echoed]".to_string()),
            ..Default::default()
        };
        let response = generate_echo_response("Hello", &config);
        assert_eq!(response, "Hello [echoed]");
    }

    #[test]
    fn test_generate_echo_response_with_prefix_and_suffix() {
        let config = EchoConfig {
            prefix: Some("> ".to_string()),
            suffix: Some(" <".to_string()),
            ..Default::default()
        };
        let response = generate_echo_response("test", &config);
        assert_eq!(response, "> test <");
    }

    #[test]
    fn test_break_into_chunks_simple() {
        let chunks = break_into_chunks("hello world", 5);
        assert!(!chunks.is_empty());
        let combined: String = chunks.join("");
        assert_eq!(combined, "hello world");
    }

    #[test]
    fn test_break_into_chunks_single_word() {
        let chunks = break_into_chunks("superlongword", 5);
        // Single word longer than chunk size - should still work
        assert!(!chunks.is_empty());
        let combined: String = chunks.join("");
        assert_eq!(combined, "superlongword");
    }

    #[test]
    fn test_break_into_chunks_empty() {
        let chunks = break_into_chunks("", 5);
        assert_eq!(chunks.len(), 1);
        assert_eq!(chunks[0], "");
    }

    #[test]
    fn test_break_into_chunks_zero_size() {
        let chunks = break_into_chunks("hello world", 0);
        assert_eq!(chunks.len(), 1);
        assert_eq!(chunks[0], "hello world");
    }

    #[test]
    fn test_break_into_chunks_large_size() {
        let text = "hello world";
        let chunks = break_into_chunks(text, 1000);
        assert_eq!(chunks.len(), 1);
        assert_eq!(chunks[0], "hello world");
    }

    #[test]
    fn test_break_into_chunks_preserves_whitespace() {
        let text = "word1 word2 word3";
        let chunks = break_into_chunks(text, 10);
        let combined: String = chunks.join("");
        assert_eq!(combined, text);
    }

    #[test]
    fn test_break_into_chunks_multiple() {
        let text = "This is a longer piece of text that should be broken into multiple chunks.";
        let chunks = break_into_chunks(text, 15);
        assert!(chunks.len() > 1);
        let combined: String = chunks.join("");
        assert_eq!(combined, text);
    }
}
